<!DOCTYPE html>



  


<html class="theme-next muse use-motion" lang>
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
















  
  
  <link href="/blog/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">







<link href="/blog/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/blog/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/blog/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/blog/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/blog/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/blog/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Hexo, NexT">





  <link rel="alternate" href="/blog/atom.xml" title="编程那点事" type="application/atom+xml">






<meta property="og:type" content="website">
<meta property="og:title" content="编程那点事">
<meta property="og:url" content="https://lhweb.gitee.io/blog/index.html">
<meta property="og:site_name" content="编程那点事">
<meta property="og:locale" content="default">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="编程那点事">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/blog/',
    scheme: 'Muse',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: 'Author'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://lhweb.gitee.io/blog/">





  <title>编程那点事</title>
  








</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="default">

  
  
    
  

  <div class="container sidebar-position-left 
  page-home">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/blog/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">编程那点事</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/blog/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            Home
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/blog/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            Archives
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            
  <section id="posts" class="posts-expand">
    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2019/10/12/手写Promise/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2019/10/12/手写Promise/" itemprop="url">手写Promise</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2019-10-12T19:58:11+08:00">
                2019-10-12
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p>Promise对象代表一个异步操作，有三种状态：pending（进行中）、fulfilled（已成功）和rejected（已失败）。一旦成功就不允许失败，一旦失败就不允许成功。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br><span class="line">463</span><br><span class="line">464</span><br><span class="line">465</span><br><span class="line">466</span><br><span class="line">467</span><br><span class="line">468</span><br><span class="line">469</span><br><span class="line">470</span><br><span class="line">471</span><br><span class="line">472</span><br><span class="line">473</span><br><span class="line">474</span><br><span class="line">475</span><br><span class="line">476</span><br><span class="line">477</span><br><span class="line">478</span><br><span class="line">479</span><br><span class="line">480</span><br><span class="line">481</span><br><span class="line">482</span><br><span class="line">483</span><br><span class="line">484</span><br><span class="line">485</span><br><span class="line">486</span><br><span class="line">487</span><br><span class="line">488</span><br><span class="line">489</span><br><span class="line">490</span><br><span class="line">491</span><br><span class="line">492</span><br><span class="line">493</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> STATUS = &#123;</span><br><span class="line"></span><br><span class="line">    PENDING: <span class="number">0</span>,</span><br><span class="line">  </span><br><span class="line">    FULFILLED: <span class="number">1</span>,</span><br><span class="line">  </span><br><span class="line">    REJECTED: <span class="number">2</span></span><br><span class="line">  </span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="built_in">Promise</span> &#123;</span><br><span class="line">   <span class="keyword">constructor</span>(<span class="params">task</span>)&#123;</span><br><span class="line">    <span class="comment">// promise初始状态</span></span><br><span class="line">    <span class="keyword">this</span>.status = STATUS.PENDING;</span><br><span class="line">    <span class="comment">// resolve时返回的数据</span></span><br><span class="line">    <span class="keyword">this</span>.resolveData  = <span class="literal">null</span>;</span><br><span class="line">    <span class="comment">// reject时返回的数据</span></span><br><span class="line">    <span class="keyword">this</span>.rejectData  = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// resolve和reject时执行的回调队列</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// promise的resolve和reject为异步响应时，即调用then时promise为</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// pending状态，则将传入then的函数加入该队列，等待promise resolve或</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// reject时执行该队列</span></span><br><span class="line">    <span class="keyword">this</span>.onFulfilledList = [];</span><br><span class="line">    <span class="keyword">this</span>.onRejectedList = [];</span><br><span class="line"></span><br><span class="line">     <span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * promise成功，执行onFulfilledList回调</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * @param &#123;*&#125; data </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.onResolve = <span class="function">(<span class="params">data</span>)=&gt;</span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.status === STATUS.PENDING)&#123;</span><br><span class="line">            <span class="keyword">this</span>.status = STATUS.FULFILLED;</span><br><span class="line">            <span class="keyword">this</span>.resolveData = data;</span><br><span class="line">            <span class="keyword">this</span>.onFulfilledList.forEach(<span class="function"><span class="params">fn</span>=&gt;</span>&#123;</span><br><span class="line">                fn(<span class="keyword">this</span>.resolveData)</span><br><span class="line">            &#125;)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">     <span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * promise失败，执行onRejectedList回调</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * @param &#123;*&#125; err </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.onReject = <span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.status === STATUS.PENDING) &#123;</span><br><span class="line">            <span class="keyword">this</span>.status = STATUS.REJECTED;</span><br><span class="line">            <span class="keyword">this</span>.rejectData = err;</span><br><span class="line">            <span class="keyword">this</span>.onRejectedList.forEach(<span class="function"><span class="params">fn</span>=&gt;</span>&#123;</span><br><span class="line">                fn(<span class="keyword">this</span>.rejectData);</span><br><span class="line">            &#125;)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * promise解析, 根据then 返回数据类型不同封装不同的promise</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * 返回，以便实现then的链式调用及Promise的thenable特性 </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * @param &#123;*当前then return数据&#125; data </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * @param &#123;*当前then的resolve&#125; resolve </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * @param &#123;*当前then的reject&#125; reject </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.resolvPromise = <span class="function">(<span class="params">data,resolve,reject</span>) =&gt;</span>&#123;</span><br><span class="line">        <span class="comment">// then return 的数据是一个promise</span></span><br><span class="line">        <span class="keyword">if</span>(data <span class="keyword">instanceof</span> <span class="built_in">Promise</span>)&#123;</span><br><span class="line">            <span class="keyword">if</span>(data.status === STATUS.PENDING)&#123;</span><br><span class="line">                data.then(<span class="function">(<span class="params">val</span>)=&gt;</span>&#123;</span><br><span class="line">                    <span class="keyword">this</span>.resolvPromise(val,resolve,reject);</span><br><span class="line">                &#125;,reject)</span><br><span class="line">            &#125;<span class="keyword">else</span> <span class="keyword">if</span>(data.status === STATUS.FULFILLED)&#123;</span><br><span class="line">                resolve(data.resolveData)</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                reject(data.rejectData)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// then return的是一个对象,若对象具有then方法，则可使用此方法作为新的then</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// Promise的thenable特性基于此</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span>(data !== <span class="literal">null</span> &amp;&amp; data <span class="keyword">instanceof</span> <span class="built_in">Object</span>) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">let</span> then = data.then</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span>(then <span class="keyword">instanceof</span> <span class="built_in">Function</span>)&#123;</span><br><span class="line">                    then.call(data,<span class="function">(<span class="params">val</span>)=&gt;</span>&#123;</span><br><span class="line">                        <span class="keyword">this</span>.resolvPromise(val,resolve,reject);</span><br><span class="line">                    &#125;,reject)</span><br><span class="line">                &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                    resolve(data)</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span>(err)&#123;</span><br><span class="line">                reject(err)</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125; </span><br><span class="line">        <span class="comment">// then return 的是基本数据或undefined</span></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            resolve(data)</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">  </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">       <span class="comment">// 执行传入的任务task</span></span><br><span class="line"></span><br><span class="line">       <span class="keyword">try</span> &#123;</span><br><span class="line"></span><br><span class="line">        task(<span class="keyword">this</span>.onResolve.bind(<span class="keyword">this</span>), <span class="keyword">this</span>.onReject.bind(<span class="keyword">this</span>))</span><br><span class="line">  </span><br><span class="line">      &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">  </span><br><span class="line">        <span class="keyword">this</span>.onReject(err)</span><br><span class="line">  </span><br><span class="line">      &#125;</span><br><span class="line">    </span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * then回调，返回一个promise</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * 说明：传入then的参数不是函数的话，直接忽略，及在返回的新promise中直接resolve或reject目前</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * promise的数据，传入then的参数是函数的话，则直接已目前promise的数据为参数执行该函数，并</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * 根据函数返回值情况确定新promise的状态</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * @param &#123;*成功&#125; onFulfilled </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * @param &#123;*失败&#125; onRejected </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"></span><br><span class="line">  then(onFulfilled,onRejected)&#123;</span><br><span class="line">      <span class="keyword">let</span> promise;</span><br><span class="line">      <span class="comment">// pending状态下将传入then的函数加入promise对应的回调队列</span></span><br><span class="line"></span><br><span class="line">      <span class="comment">// 等待promise状态改变后执行</span></span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span>(<span class="keyword">this</span>.status === STATUS.PENDING)&#123;</span><br><span class="line">          promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve,reject</span>)=&gt;</span>&#123;</span><br><span class="line">              <span class="keyword">this</span>.onFulfilledList.push(<span class="function"><span class="params">()</span>=&gt;</span>&#123;</span><br><span class="line">                  <span class="comment">// 传入then的参数不是函数则忽略</span></span><br><span class="line">                 <span class="keyword">if</span>(!(onFulfilled <span class="keyword">instanceof</span> <span class="built_in">Function</span>)) &#123;</span><br><span class="line">                     resolve(<span class="keyword">this</span>.resolveData);</span><br><span class="line">                 &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                     <span class="keyword">let</span> data = onFulfilled(<span class="keyword">this</span>.resolveData);</span><br><span class="line"></span><br><span class="line">                     <span class="keyword">this</span>.resolvPromise(data,resolve,reject);</span><br><span class="line">                 &#125;</span><br><span class="line"></span><br><span class="line">              &#125;)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">this</span>.onRejectedList.push(<span class="function"><span class="params">()</span>=&gt;</span>&#123;</span><br><span class="line">            <span class="comment">// 传入then的参数不是函数则忽略</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(!(onRejected <span class="keyword">instanceof</span> <span class="built_in">Function</span>))&#123;</span><br><span class="line">                reject(<span class="keyword">this</span>.resolveData)</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">let</span> data = onRejected(<span class="keyword">this</span>.resolveData)</span><br><span class="line"></span><br><span class="line">                <span class="keyword">this</span>.resolvPromise(data,resolve,reject)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;)</span><br><span class="line"></span><br><span class="line">          &#125;)</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// fulfilled状态下以promise的resolveData为参数执行传入then的</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 成功回调函数，再根据此函数的返回值封装新的promise返回</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">this</span>.status === STATUS.FULFILLED) &#123;</span><br><span class="line"></span><br><span class="line">        promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  </span><br><span class="line">          <span class="comment">// 传入then的参数不是函数则忽略，直接resolve</span></span><br><span class="line">  </span><br><span class="line">          <span class="keyword">if</span>(!(onFulfilled <span class="keyword">instanceof</span> <span class="built_in">Function</span>)) &#123;</span><br><span class="line">  </span><br><span class="line">            resolve(<span class="keyword">this</span>.resolveData)</span><br><span class="line">  </span><br><span class="line">          &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  </span><br><span class="line">            <span class="keyword">let</span> data = onFulfilled(<span class="keyword">this</span>.resolveData)</span><br><span class="line">  </span><br><span class="line">            <span class="keyword">this</span>.resolvePromise(data, resolve, reject)</span><br><span class="line">  </span><br><span class="line">          &#125;      </span><br><span class="line">  </span><br><span class="line">        &#125;)</span><br><span class="line">  </span><br><span class="line">      &#125;</span><br><span class="line">  </span><br><span class="line">      <span class="comment">// rejected状态类似fulfilled状态</span></span><br><span class="line"></span><br><span class="line">      <span class="keyword">else</span> &#123;</span><br><span class="line"></span><br><span class="line">        promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  </span><br><span class="line">          <span class="comment">// 传入then的参数不是函数则忽略，直接reject</span></span><br><span class="line">  </span><br><span class="line">          <span class="keyword">if</span>(!(onRejected <span class="keyword">instanceof</span> <span class="built_in">Function</span>)) &#123;</span><br><span class="line">  </span><br><span class="line">            reject(<span class="keyword">this</span>.rejectData)</span><br><span class="line">  </span><br><span class="line">          &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  </span><br><span class="line">            <span class="keyword">let</span> data = onRejected(<span class="keyword">this</span>.rejectData)</span><br><span class="line">  </span><br><span class="line">            <span class="keyword">this</span>.resolvePromise(data, resolve, reject)</span><br><span class="line">  </span><br><span class="line">          &#125;        </span><br><span class="line">  </span><br><span class="line">        &#125;)</span><br><span class="line">  </span><br><span class="line">      &#125;</span><br><span class="line">  </span><br><span class="line">      <span class="keyword">return</span> promise</span><br><span class="line">  </span><br><span class="line">    &#125;</span><br><span class="line">  </span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * catch方法</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * @param &#123;*reject函数&#125; rejectFn </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="keyword">catch</span>(rejectFn) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//不是函数直接返回</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(!(rejectFn <span class="keyword">instanceof</span> <span class="built_in">Function</span>)) &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(<span class="keyword">this</span>.status === STATUS.PENDING) &#123;  </span><br><span class="line"></span><br><span class="line">        <span class="keyword">this</span>.onRejectedList.push(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  </span><br><span class="line">          <span class="comment">// 没有错误信息则不执行catch中的函数</span></span><br><span class="line">  </span><br><span class="line">          <span class="keyword">if</span>(<span class="keyword">this</span>.rejectData !== <span class="literal">null</span>) &#123;</span><br><span class="line">  </span><br><span class="line">            rejectFn(<span class="keyword">this</span>.rejectData)</span><br><span class="line">  </span><br><span class="line">          &#125; </span><br><span class="line">  </span><br><span class="line">        &#125;)</span><br><span class="line">  </span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">this</span>.status = STATUS.REJECTED) &#123;</span><br><span class="line">  </span><br><span class="line">        <span class="comment">// 没有错误信息则不执行catch中的函数</span></span><br><span class="line">  </span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.rejectData !== <span class="literal">null</span>) &#123;</span><br><span class="line">  </span><br><span class="line">          rejectFn(<span class="keyword">this</span>.rejectData)</span><br><span class="line">  </span><br><span class="line">        &#125;    </span><br><span class="line">  </span><br><span class="line">      &#125;      </span><br><span class="line">  </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * resolve方法，</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * value为promise直接返回返回一个以value为resolveData的完成态promise</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * @param &#123;*&#125; value </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> resolve(value) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(value <span class="keyword">instanceof</span> <span class="built_in">Promise</span>) &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> value</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;    </span><br><span class="line"></span><br><span class="line">      resolve(value)</span><br><span class="line"></span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * reject方法，类似resolve方法</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * @param &#123;*&#125; value </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> reject(value) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(value <span class="keyword">instanceof</span> <span class="built_in">Promise</span>) &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> value</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;    </span><br><span class="line"></span><br><span class="line">      reject(value)</span><br><span class="line"></span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * all方法，返回一个新的promise</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * 参数为promise数组</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * 成功的时候返回的是一个结果数组，而失败的时候则返回最先被reject失败状态的值。</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * @param &#123;*&#125; promiseArray</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> all(promiseArray) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(!(promiseArray <span class="keyword">instanceof</span> <span class="built_in">Array</span>)) &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">"parameter must be array"</span>)</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> result = []</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span>(promiseArray.length === <span class="number">0</span>) &#123;</span><br><span class="line"></span><br><span class="line">        resolve(result)</span><br><span class="line"></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"></span><br><span class="line">        promiseArray.forEach(<span class="function">(<span class="params">item, index</span>) =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">          <span class="keyword">if</span>(item <span class="keyword">instanceof</span> <span class="built_in">Promise</span>) &#123;</span><br><span class="line"></span><br><span class="line">            item.then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">              result[index] = res</span><br><span class="line"></span><br><span class="line">              i++</span><br><span class="line"></span><br><span class="line">              <span class="keyword">if</span>(i === promiseArray.length) &#123;</span><br><span class="line"></span><br><span class="line">                resolve(result)</span><br><span class="line"></span><br><span class="line">              &#125;</span><br><span class="line"></span><br><span class="line">            &#125;, <span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">              reject(err)</span><br><span class="line"></span><br><span class="line">            &#125;)</span><br><span class="line"></span><br><span class="line">          &#125; </span><br><span class="line"></span><br><span class="line">          <span class="comment">// 如果传入的不是promise，则直接作为结果填入结果数组中</span></span><br><span class="line"></span><br><span class="line">          <span class="keyword">else</span> &#123;</span><br><span class="line"></span><br><span class="line">            result[index] = item</span><br><span class="line"></span><br><span class="line">            i++</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(i === promiseArray.length) &#123;</span><br><span class="line"></span><br><span class="line">              resolve(result)</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">        &#125;)</span><br><span class="line"></span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">    &#125;)  </span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * race方法，返回一个新的promise</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * 参数为promise数组</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * 返回最先执行完的promise的结果，不论resolve还是reject</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  * @param &#123;*&#125; promiseArray </span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> race(promiseArray) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(!(promiseArray <span class="keyword">instanceof</span> <span class="built_in">Array</span>)) &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">"parameter must be array"</span>)</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 标识符，有一个promise执行完成设为true，返回结果</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> flag = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">      promiseArray.forEach(<span class="function">(<span class="params">item</span>) =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(item <span class="keyword">instanceof</span> <span class="built_in">Promise</span>) &#123;</span><br><span class="line"></span><br><span class="line">          item.then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(!flag) &#123;</span><br><span class="line"></span><br><span class="line">              flag = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">              resolve(res)</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">          &#125;, <span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(!flag) &#123;</span><br><span class="line"></span><br><span class="line">              flag = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">              reject(err)</span><br><span class="line"></span><br><span class="line">            &#125;          </span><br><span class="line"></span><br><span class="line">          &#125;)</span><br><span class="line"></span><br><span class="line">        &#125; </span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果传入的不是promise，则直接作为结果</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line"></span><br><span class="line">          <span class="keyword">if</span>(!flag) &#123;</span><br><span class="line"></span><br><span class="line">            flag = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">            resolve(item)</span><br><span class="line"></span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">      &#125;)</span><br><span class="line"></span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2019/10/10/Proxy代理语法/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2019/10/10/Proxy代理语法/" itemprop="url">'Proxy代理语法'</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2019-10-10T09:13:54+08:00">
                2019-10-10
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2019/10/09/decorator修饰器语法/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2019/10/09/decorator修饰器语法/" itemprop="url">decorator修饰器语法</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2019-10-09T15:32:03+08:00">
                2019-10-09
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h1 id="什么是decorator"><a href="#什么是decorator" class="headerlink" title="什么是decorator"></a>什么是decorator</h1><ul>
<li>Decorator是在声明阶段实现类与类成员注解的一种语法</li>
</ul>
<figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">@log</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Numberic</span> &#123;</span></span><br><span class="line">     @readonly PI = Math.PI;</span><br><span class="line">      </span><br><span class="line">     @validate</span><br><span class="line">     add(...nums)&#123;</span><br><span class="line">       <span class="keyword">return</span> nums.reduce(<span class="function"><span class="params">(p,n)</span>=&gt;</span>(p+n),<span class="number">0</span>)</span><br><span class="line">     &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight processing"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">function <span class="built_in">log</span>(target)&#123;</span><br><span class="line">    <span class="keyword">const</span> desc = <span class="keyword">Object</span>.getOwnPropertyDescriptors(target.prototype);</span><br><span class="line">     </span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">const</span> <span class="built_in">key</span> of <span class="keyword">Object</span>.keys(desc))&#123;</span><br><span class="line">       <span class="keyword">if</span>(<span class="built_in">key</span> === <span class="string">'constructor'</span>)&#123;</span><br><span class="line">          <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">const</span> func = desc[<span class="built_in">key</span>].value;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span>(<span class="string">'function'</span>=== typeof func) &#123;</span><br><span class="line">         <span class="keyword">Object</span>.defineProperty(target.prototype,<span class="built_in">key</span>,&#123;</span><br><span class="line">          value(...args)&#123;</span><br><span class="line">           console.<span class="built_in">log</span>(<span class="string">'before'</span>+<span class="built_in">key</span>);</span><br><span class="line">           <span class="keyword">const</span> ret = func.apply(<span class="keyword">this</span>,args);</span><br><span class="line">           console.<span class="built_in">log</span>(<span class="string">'before'</span>+<span class="built_in">key</span>);</span><br><span class="line">           <span class="keyword">return</span> ret</span><br><span class="line">          &#125;</span><br><span class="line">             </span><br><span class="line">         &#125;)</span><br><span class="line">       &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight actionscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">readonly</span><span class="params">(target,key,desc)</span></span>&#123;</span><br><span class="line">   desc.writable = <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight actionscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">validate</span><span class="params">(target,key,desc)</span></span>&#123;</span><br><span class="line">    <span class="keyword">const</span> func = desc.value;</span><br><span class="line">    desc.value = <span class="function"><span class="keyword">function</span><span class="params">(<span class="rest_arg">...args</span>)</span></span>&#123;</span><br><span class="line">      <span class="keyword">for</span>(let num of args)&#123;</span><br><span class="line">          <span class="keyword">if</span>(<span class="string">'number'</span> === <span class="keyword">typeof</span> num)&#123;</span><br><span class="line">              <span class="keyword">throw</span> <span class="keyword">new</span> Error(`<span class="string">"$&#123;num&#125;"</span> <span class="keyword">is</span> not a number`);</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line">       <span class="keyword">return</span> func.apply(<span class="keyword">this</span>,args);</span><br><span class="line">   &#125;     </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2019/10/07/MobX-State-Tree数据组件化开发-3-：选择正确的types-xxx/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2019/10/07/MobX-State-Tree数据组件化开发-3-：选择正确的types-xxx/" itemprop="url">[MobX State Tree数据组件化开发][3]：选择正确的types.xxx</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2019-10-07T17:53:53+08:00">
                2019-10-07
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p>定义Model时，需要正确地定义props中各字段的类型。本文将对MST提供的各种类型以及类型的工厂方法进行简单的介绍，方便同学们在定义props时挑选正确的类型。</p>
<h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h2><p>定义props之前，有一个前提是，你已经明确地知道这个Model中状态的数据类型。</p>
<p>如果Model用于存放由后端API返回的数据，那么一定要和后端确认返回值在所有情况下的类型。比如，某个字段在没有值的时候你以为会给一个’’，而后端却给了个null；或者某个数组你以为会给你一个空数组[]，他又甩你一个null。如果你不打算自己编写一个标准化数据的方法，那一定要和数据的提供方确定这些细节。</p>
<h2 id="基础类型"><a href="#基础类型" class="headerlink" title="基础类型"></a>基础类型</h2><h3 id="types-string"><a href="#types-string" class="headerlink" title="types.string"></a>types.string</h3><p>定义一个字符串类型字段。</p>
<h3 id="types-number"><a href="#types-number" class="headerlink" title="types.number"></a>types.number</h3><p>定义一个数值类型字段。</p>
<h3 id="types-boolean"><a href="#types-boolean" class="headerlink" title="types.boolean"></a>types.boolean</h3><p>定义一个布尔类型字段。</p>
<h3 id="types-integer"><a href="#types-integer" class="headerlink" title="types.integer"></a>types.integer</h3><p>定义一个整数类型字段。</p>
<p>注意，即使是TypeScript中也没有“整数”这个类型，在编码时，传入一个带小数的值TypeScript也无法发现其中的类型错误。如无必要，请使用types.number。</p>
<h3 id="types-Date"><a href="#types-Date" class="headerlink" title="types.Date"></a>types.Date</h3><p>定义一个日期类型字段。</p>
<p>这个类型存储的值是标准的Date对象。在设置值时，可以选择传入数值类型的时间戳或者Date对象。</p>
<figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">export const Model = types</span><br><span class="line">    .model(&#123;</span><br><span class="line">        <span class="built_in">date</span>: types.<span class="built_in">Date</span> </span><br><span class="line">    &#125;)</span><br><span class="line">    .actions(<span class="built_in">self</span> =&gt; (&#123;</span><br><span class="line">        setDate (val: <span class="built_in">Date</span> | number) &#123;</span><br><span class="line">            <span class="built_in">self</span>.<span class="built_in">date</span> = <span class="built_in">date</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br></pre></td></tr></table></figure>
<h3 id="types-null"><a href="#types-null" class="headerlink" title="types.null"></a>types.null</h3><p>定义一个值为null的类型字段。</p>
<h3 id="types-undefined"><a href="#types-undefined" class="headerlink" title="types.undefined"></a>types.undefined</h3><p>定义一个值为undefined的类型字段。</p>
<h2 id="复合类型"><a href="#复合类型" class="headerlink" title="复合类型"></a>复合类型</h2><h3 id="types-model"><a href="#types-model" class="headerlink" title="types.model"></a>types.model</h3><p>定义一个对象类型的字段。</p>
<h3 id="types-array"><a href="#types-array" class="headerlink" title="types.array"></a>types.array</h3><p>定义一个数组类型的字段。<br><code>types.array(types.string);</code></p>
<p>上面的代码定义了一个字符串数组的类型。</p>
<h3 id="types-map"><a href="#types-map" class="headerlink" title="types.map"></a>types.map</h3><p>定义一个map类型的字段。该map的key都为字符串类型，map的值都为指定类型。</p>
<p><code>types.map(types.number);</code></p>
<h2 id="可选类型，types-optional"><a href="#可选类型，types-optional" class="headerlink" title="可选类型，types.optional"></a>可选类型，types.optional</h2><p>根据传入的参数，定义一个带有默认值的可选类型。</p>
<p>types.optional是一个方法，方法有两个参数，第一个参数是数据的真实类型，第二个参数是数据的默认值。</p>
<p><code>types.optional(types.number, 1);</code></p>
<p>上面的代码定义了一个默认值为1的数值类型。</p>
<p>注意，types.array或者types.map定义的类型自带默认值（array为[]，map为{}），也就是说，下面两种定义的结果是一样的：</p>
<figure class="highlight elm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">// 使用types.optional</span><br><span class="line">types.optional(types.array(types.number), []);</span><br><span class="line">types.optional(types.map(types.number), &#123;&#125;);</span><br><span class="line"></span><br><span class="line">// 不使用types.optional</span><br><span class="line">types.array(types.number);</span><br><span class="line">types.map(types.number);</span><br></pre></td></tr></table></figure>
<p>如果要设置的默认值与types.array或types.map自带的默认值相同，那么就不需要使用types.optional。</p>
<h2 id="自定义类型-types-custom"><a href="#自定义类型-types-custom" class="headerlink" title="自定义类型 types.custom"></a>自定义类型 types.custom</h2><p>如果想控制类型更底层的如序列化和反序列化、类型校验等细节，或者根据一个class或interface来定义类型，可以使用types.custom定义自定义类型。</p>
<figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">class <span class="built_in">Decimal</span> &#123;</span><br><span class="line">    <span class="params">...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">const DecimalPrimitive = types.custom&lt;<span class="built_in">string</span>, <span class="built_in">Decimal</span>&gt;(&#123;</span><br><span class="line">    name: <span class="string">"Decimal"</span>,</span><br><span class="line">    fromSnapshot(value: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">new</span> <span class="built_in">Decimal</span>(value)</span><br><span class="line">    &#125;,</span><br><span class="line">    toSnapshot(value: <span class="built_in">Decimal</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> value.toString()</span><br><span class="line">    &#125;,</span><br><span class="line">    isTargetType(value: <span class="built_in">string</span> | <span class="built_in">Decimal</span>): <span class="built_in">boolean</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> value instanceof <span class="built_in">Decimal</span></span><br><span class="line">    &#125;,</span><br><span class="line">    getValidationMessage(value: <span class="built_in">string</span>): <span class="built_in">string</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (/^-?\d+\.\d+$/.test(value)) <span class="keyword">return</span> <span class="string">""</span> <span class="comment">// OK</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">`'$&#123;value&#125;' doesn't look like a valid decimal number`</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>上面的代码定义了一个Decimal类型。</p>
<h2 id="联合类型，types-union"><a href="#联合类型，types-union" class="headerlink" title="联合类型，types.union"></a>联合类型，types.union</h2><p>实际开发中也许会遇到这样的情况：一个值的类型可能是字符串，也可能是数值。那我们就可以使用types.union定义联合类型：</p>
<p><code>types.union(types.number, types.string);</code></p>
<p>联合类型可以有任意个联合的类型。</p>
<h2 id="字面值类型，types-literal"><a href="#字面值类型，types-literal" class="headerlink" title="字面值类型，types.literal"></a>字面值类型，types.literal</h2><p>字面值类型可以限制存储的内容与给定的值严格相等。</p>
<p>比如使用types.literal(‘male’)定义的状态值只能为’male’。</p>
<p>实际上，上面提到过的types.null以及types.undefined就是字面值类型：</p>
<figure class="highlight actionscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> NullType = types.literal(<span class="literal">null</span>);</span><br><span class="line"><span class="keyword">const</span> UndefinedType = types.literal(<span class="literal">undefined</span>);</span><br></pre></td></tr></table></figure>
<p>搭配联合类型，可以这样定义一个性别类型：</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> GenderType = types.<span class="keyword">union</span>(types.literal(<span class="string">'male'</span>), types.literal(<span class="string">'female'</span>));</span><br></pre></td></tr></table></figure>
<h2 id="枚举类型，types-enumeration"><a href="#枚举类型，types-enumeration" class="headerlink" title="枚举类型，types.enumeration"></a>枚举类型，types.enumeration</h2><p>枚举类型可以看作是联合类型以及字面值类型的一层封装，比如上面的性别可以使用枚举类型来定义：</p>
<p><code>const GenderType = types.enumeration(&#39;Gender&#39;, [&#39;male&#39;, &#39;female&#39;]);</code></p>
<p>方法的第一个参数是可选的，表示枚举类型的名称。第二个参数传入的是字面值数组。</p>
<p>在TypeScript环境下，可以这样搭配TypeScript枚举使用：</p>
<figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">Gender</span> &#123;</span></span><br><span class="line">    male,</span><br><span class="line">    female</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">const GenderType = types.enumeration&lt;Gender&gt;(<span class="string">'Gender'</span>, Object.values(Gender));</span><br></pre></td></tr></table></figure>
<h2 id="可undefined类型，types-maybe"><a href="#可undefined类型，types-maybe" class="headerlink" title="可undefined类型，types.maybe"></a>可undefined类型，types.maybe</h2><p>定义一个可能为undefined的字段，并自带默认值undefined。</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">types.maybe(<span class="class"><span class="keyword">type</span>)</span></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line">types.optional(types.union(<span class="class"><span class="keyword">type</span>, <span class="title">types</span>.<span class="title">literal</span></span>(undefined)), undefined)</span><br></pre></td></tr></table></figure>
<h2 id="可空类型，types-maybeNull"><a href="#可空类型，types-maybeNull" class="headerlink" title="可空类型，types.maybeNull"></a>可空类型，types.maybeNull</h2><p>与types.maybe类似，将undefined替换成了null。</p>
<figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">types.maybeNull(<span class="class"><span class="keyword">type</span>)</span></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line">types.optional(types.union(<span class="class"><span class="keyword">type</span>, <span class="title">types</span>.<span class="title">literal</span></span>(<span class="keyword">null</span>)), <span class="keyword">null</span>)</span><br></pre></td></tr></table></figure>
<h2 id="不可不类型，types-frozen"><a href="#不可不类型，types-frozen" class="headerlink" title="不可不类型，types.frozen"></a>不可不类型，types.frozen</h2><p>frozen意为“冻结的”，types.frozen方法用来定义一个immutable类型，并且存放的值必须是可序列化的。</p>
<p>当数据的类型不确定时，在TypeScript中通常将值的类型设置为any，而在MST中，就需要使用types.frozen定义。</p>
<figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">const Model = types</span><br><span class="line">    .model(<span class="string">'Model'</span>, &#123;</span><br><span class="line">        anyData: types.<span class="keyword">frozen</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">    .actions(<span class="built_in">self</span> =&gt; (&#123;</span><br><span class="line">        setAnyData (<span class="built_in">data</span>: any) &#123;</span><br><span class="line">            <span class="built_in">self</span>.anyData = <span class="built_in">data</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br></pre></td></tr></table></figure>
<p>在MST看来，使用types.frozen定义类型的状态值是不可变的，所以会出现这样的情况：</p>
<figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">model.anyData</span> = &#123;a: <span class="number">1</span>, b: <span class="number">2</span>&#125;<span class="comment">; // ok, reactive</span></span><br><span class="line"><span class="attr">model.anyData.b</span> = <span class="number">3</span><span class="comment">; // not reactive</span></span><br></pre></td></tr></table></figure>
<p>也就是只有设置一个新的值给这个字段，相关的observer才会响应状态的更新。而修改这个字段内部的某个值，是不会被捕捉到的。</p>
<h2 id="滞后类型，types-late"><a href="#滞后类型，types-late" class="headerlink" title="滞后类型，types.late"></a>滞后类型，types.late</h2><p>有时候会出现这样的需求，需要一个Model A，在A中，存在类型为A本身的字段。</p>
<p>如果这样写：</p>
<figure class="highlight delphi"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> A = types</span><br><span class="line">    .model(<span class="string">'A'</span>, <span class="comment">&#123;</span></span><br><span class="line"><span class="comment">        a: types.maybe(A), // 使用mabe避免无限循环</span></span><br><span class="line"><span class="comment">    &#125;</span>);</span><br></pre></td></tr></table></figure>
<p>会提示Block-scoped variable ‘A’ used before its declaration，也就是在A定义完成之前就试图使用他，这样是不被允许的。</p>
<p><img src="/blog/2019/10/07/MobX-State-Tree数据组件化开发-3-：选择正确的types-xxx/168a311b49e356b1.jpg" alt></p>
<p>这个时候就需要使用types.late：</p>
<figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">const A = types</span><br><span class="line">  .model(<span class="string">'A'</span>, &#123;</span><br><span class="line">    a: types.maybe(types.late(<span class="function"><span class="params">()</span> =&gt;</span> A))</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure>
<p>types.late需要传入一个方法，在方法中返回A，这样就可以避开上面报错的问题。</p>
<h2 id="提纯类型，types-refinement"><a href="#提纯类型，types-refinement" class="headerlink" title="提纯类型，types.refinement"></a>提纯类型，types.refinement</h2><p>types.refinement可以在其他类型的基础上，添加额外的类型校验规则。</p>
<p>比如需要定义一个email字段，类型为字符串但必须满足email的标准格式，就可以这样做：</p>
<figure class="highlight mel"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">const EmailType = types.refinement(</span><br><span class="line">    <span class="string">'Email'</span>,</span><br><span class="line">    types.<span class="keyword">string</span>,</span><br><span class="line">    (<span class="keyword">snapshot</span>) =&gt; /^[a-zA-Z_1<span class="number">-9</span>]+@\.[a-z]+/.test(<span class="keyword">snapshot</span>), <span class="comment">// 校验是否符合email格式</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<h2 id="引用与标识类型"><a href="#引用与标识类型" class="headerlink" title="引用与标识类型"></a>引用与标识类型</h2><p>拿上一篇文章中的TodoList作为例子，我们在对Todo列表中的某一个Todo进行编辑的时候，需要通过id跟踪这个Todo，在提交编辑结果时，通过这个id找到对应的Todo对象，然后进行更新。<br>这种需要跟踪、查找的需求很常见，写多了也觉得麻烦。<br>好在MST提供了一个优雅的解决方案：引用类型和标识类型。<br>这两者需要搭配使用才能发挥作用：</p>
<h3 id="定义标识，types-identifier"><a href="#定义标识，types-identifier" class="headerlink" title="定义标识，types.identifier"></a>定义标识，types.identifier</h3><p>标识就是数据对象的唯一标识字段，这个字段的值在库中保持唯一，也就是primary_key。</p>
<p>比如上一篇文章中的TodoItem，可以改造为：</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> TodoItem = types</span><br><span class="line">  .model(<span class="string">'TodoItem'</span>, &#123;</span><br><span class="line">    id: types.identifier,</span><br><span class="line">    title: types.<span class="keyword">string</span>,</span><br><span class="line">    done: types.<span class="keyword">boolean</span>,</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure>
<h3 id="使用引用类型进行跟踪，types-reference"><a href="#使用引用类型进行跟踪，types-reference" class="headerlink" title="使用引用类型进行跟踪，types.reference"></a>使用引用类型进行跟踪，types.reference</h3><p>改造TodoList：</p>
<figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">export const TodoList = types</span><br><span class="line">  .model(<span class="string">'TodoList'</span>, &#123;</span><br><span class="line">    <span class="params">...</span></span><br><span class="line">    <span class="built_in">list</span>: types.<span class="built_in">array</span>(TodoItem),</span><br><span class="line">    editTarget: types.reference(TodoItem),</span><br><span class="line">    <span class="params">...</span></span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure>
<p>然后在创建Model实例，或者applySnapshot的时候，可以将editTarget的值设定为正在编辑的TodoItem的id值，MST就会自动在list中查找id相同的TodoItem：</p>
<figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">const todoList = TodoList.create(&#123;</span><br><span class="line"><span class="symbol">   list:</span> [</span><br><span class="line">       &#123;<span class="string">id:</span> <span class="string">'1'</span>, <span class="string">title:</span> <span class="string">'Todo 1'</span>, <span class="string">done:</span> <span class="literal">true</span>&#125;,</span><br><span class="line">       &#123;<span class="string">id:</span> <span class="string">'2'</span>, <span class="string">title:</span> <span class="string">'Todo 2'</span>, <span class="string">done:</span> <span class="literal">true</span>&#125;,</span><br><span class="line">       ...</span><br><span class="line">   ],</span><br><span class="line"><span class="symbol">   editTarget:</span> <span class="string">'1'</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">//此时的editTarget就是list中id为'1'的TodoItem对象</span></span><br><span class="line">console.log(todoList.list[<span class="number">0</span>] === todoList.editTarget); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line">todoList.editTarget = todoItem2; <span class="comment">// todoItem2为id为'2'的TodoItem对象</span></span><br><span class="line">console.log(getSnapshot(todoList).editTarget === <span class="string">'2'</span>); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line">todoList.editTarget = <span class="string">'2'</span> <span class="keyword">as</span> any;</span><br><span class="line">console.log(getSnapshot(todoList).editTarget === <span class="string">'2'</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure>
<p>上面的代码说明，reference类型的字段本质上维护的是目标的标识字段值，并且，除了将目标对象赋值给reference字段外，将目标标识字段值赋值给reference字段的效果是一样的。</p>
<p>另外，reference不仅仅能搭配array使用，也能在map中查找：</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> TodoList = types.model(<span class="string">'TodoList'</span>, &#123;</span><br><span class="line">    todoMap: types.<span class="built_in">map</span>(TodoItem),</span><br><span class="line">    editTarget: types.reference(TodoItem)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>甚至，MST也允许你自定义查找器（resolver），给types.reference指定第二个参数，比如官网的这个例子：</p>
<figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> User = types.model(&#123;</span><br><span class="line">    id: types.identifier,</span><br><span class="line">    name: types.<span class="keyword">string</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> UserByNameReference = types.maybeNull(</span><br><span class="line">    types.reference(User, &#123;</span><br><span class="line">        <span class="comment">// given an identifier, find the user</span></span><br><span class="line">        <span class="keyword">get</span>(identifier <span class="comment">/* string */</span>, parent: any <span class="comment">/*Store*/</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> parent.users.find(u =&gt; u.name === identifier) || <span class="literal">null</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="comment">// given a user, produce the identifier that should be stored</span></span><br><span class="line">        <span class="keyword">set</span>(<span class="keyword">value</span> <span class="comment">/* User */</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">value</span>.name</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Store = types.model(&#123;</span><br><span class="line">    users: types.array(User),</span><br><span class="line">    selection: UserByNameReference</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> s = Store.create(&#123;</span><br><span class="line">    users: [&#123; id: <span class="string">"1"</span>, name: <span class="string">"Michel"</span> &#125;, &#123; id: <span class="string">"2"</span>, name: <span class="string">"Mattia"</span> &#125;],</span><br><span class="line">    selection: <span class="string">"Mattia"</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<h3 id="types-identifierNumber"><a href="#types-identifierNumber" class="headerlink" title="types.identifierNumber"></a>types.identifierNumber</h3><p>若对象的唯一标识字段的值为数值类型，那么可以使用types.identifierNumber代替types.identifier。</p>
<h3 id="types-safeReference"><a href="#types-safeReference" class="headerlink" title="types.safeReference"></a>types.safeReference</h3><p>这是一个“安全”的引用类型：</p>
<figure class="highlight elm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">const</span> <span class="type">Todo</span> = types.model(&#123; id: types.identifier &#125;)</span><br><span class="line"><span class="title">const</span> <span class="type">Store</span> = types.model(&#123;</span><br><span class="line">    todos: types.array(<span class="type">Todo</span>),</span><br><span class="line">    selectedTodo: types.safeReference(<span class="type">Todo</span>)</span><br><span class="line">&#125;)；</span><br></pre></td></tr></table></figure>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2019/10/07/MobX-State-Tree数据组件化开发-2-：实例-TodoList/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2019/10/07/MobX-State-Tree数据组件化开发-2-：实例-TodoList/" itemprop="url">[MobX State Tree数据组件化开发][2]：实例-TodoList</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2019-10-07T17:40:59+08:00">
                2019-10-07
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p>上一篇文章MST基础中简单地介绍了MST的几个基本概念和相关API，本文将带大家搭配React实现一个TodoList。</p>
<h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><p>为了省去枯燥的项目搭建过程，本文选择使用stackblitz平台来编辑我们的代码。</p>
<p>同学们可以点击上面的地址fork一个starter项目，项目中已经配置好MST以及React相关的依赖，并且包含了一个简单的Counter demo，后面将在这个starter的基础上进行开发。</p>
<h2 id="项目结构-amp-规范说明"><a href="#项目结构-amp-规范说明" class="headerlink" title="项目结构&amp;规范说明"></a>项目结构&amp;规范说明</h2><p>从上面的地址进入后，你会得到一个包含以下目录结构的初始项目。</p>
<p><img src="/blog/2019/10/07/MobX-State-Tree数据组件化开发-2-：实例-TodoList/1689d4e977365753.jpg" alt></p>
<p>其中，目录components用于存放React组件，目录models用于存放MST Model。</p>
<p>整个应用的Root Model在models/index.ts文件中定义：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; types &#125; <span class="keyword">from</span> <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; Counter &#125; <span class="keyword">from</span> <span class="string">'./Counter'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> Root = types</span><br><span class="line">  .model(<span class="string">'Root'</span>, &#123;</span><br><span class="line">    counter: types.optional(Counter, &#123;&#125;),</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure>
<p>定义好的Root Model会在项目的入口index.tsx文件中被引入，并创建实例对象，然后使用mobx-react提供的Provider组件将Root Model的实例对象传递到应用的Context中：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; render &#125; <span class="keyword">from</span> <span class="string">'react-dom'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; Provider &#125; <span class="keyword">from</span> <span class="string">'mobx-react'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; Root &#125; <span class="keyword">from</span> <span class="string">'./models'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; ModelInjector &#125; <span class="keyword">from</span> <span class="string">'./components/ModelInjector'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'./style.css'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; Counter &#125; <span class="keyword">from</span> <span class="string">'./components/Counter'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> root = Root.create(&#123;&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> ConnectedCounter = <span class="function"><span class="params">()</span> =&gt;</span> (</span><br><span class="line">  &lt;ModelInjector&gt;</span><br><span class="line">    &#123;(root) =&gt; <span class="xml"><span class="tag">&lt;<span class="name">Counter</span> <span class="attr">model</span>=<span class="string">&#123;root.counter&#125;/</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">ModelInjector</span>&gt;</span></span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">App</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    &lt;Provider root=&#123;root&#125;&gt;</span><br><span class="line">      &lt;ConnectedCounter/&gt;</span><br><span class="line">    &lt;<span class="regexp">/Provider&gt;</span></span><br><span class="line"><span class="regexp">  );</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">render(&lt;App /</span>&gt;, <span class="built_in">document</span>.getElementById(<span class="string">'root'</span>));</span><br></pre></td></tr></table></figure>
<p>项目提供了一个名为ModelInjector的组件，index.tsx代码中，使用ModelInjector组件对Counter组件进行了一个包装，将root.counter这个节点Model作为props传给了Counter组件。在本文以及后续的文章中，将会沿用这样的方式在组件与Model之间建立连接。</p>
<p>这样做的好处是，可以让TypeScript的静态类型约束覆盖到整个应用，开发过程中可以享受到类型带来的便利：</p>
<p><img src="/blog/2019/10/07/MobX-State-Tree数据组件化开发-2-：实例-TodoList/1689d4da62714640.jpg" alt></p>
<p>ModelInjector组件的实现比较简单，可以在项目中自行查看。</p>
<h2 id="确定目标"><a href="#确定目标" class="headerlink" title="确定目标"></a>确定目标</h2><p>在开始动手编码之前，必须明确要做的这个东西是什么样的。</p>
<p>我们要做的这款TodoList大家应该比较熟悉：</p>
<p><img src="/blog/2019/10/07/MobX-State-Tree数据组件化开发-2-：实例-TodoList/1689d4d0c2c9af1c.jpg" alt></p>
<p>这款TodoList来自TodoMVC。</p>
<p>由于本文的主题不在UI的实现上，我们可以复用他的DOM结构和CSS，这会省去不少功夫。</p>
<h2 id="定义Model"><a href="#定义Model" class="headerlink" title="定义Model"></a>定义Model</h2><h3 id="TodoItem"><a href="#TodoItem" class="headerlink" title="TodoItem"></a>TodoItem</h3><p>从最基础的开始。TodoList的基本单位就是TodoItem，TodoItem具备的属性是他的id（用于编辑、删除时进行跟踪）、title，以及是否完成的标识done，所以可以得出：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// models/TodoItem.ts</span></span><br><span class="line"><span class="keyword">import</span> &#123; types, Instance &#125; <span class="keyword">from</span> <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> TodoItem = types</span><br><span class="line">  .model(<span class="string">'TodoItem'</span>, &#123;</span><br><span class="line">    id: types.string,</span><br><span class="line">    title: types.string,</span><br><span class="line">    done: types.boolean,</span><br><span class="line">  &#125;)</span><br><span class="line">  .actions(<span class="function"><span class="params">self</span> =&gt;</span> (&#123;</span><br><span class="line">    switchDone(done?: <span class="built_in">boolean</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">typeof</span> done === <span class="string">'boolean'</span>) &#123;</span><br><span class="line">        self.done = done;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        self.done = !self.done;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;));</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> TodoItemInstance = Instance&lt;<span class="keyword">typeof</span> TodoItem&gt;;</span><br></pre></td></tr></table></figure>
<p>新建models/TodoItem.ts文件，写入上面的代码。</p>
<p>细心的同学会发现，上面代码中还export了一个type定义TodoItemInstance。这个type表示的是TodoItem这个Model的实例类型，可以在定义React组件的props类型时使用。</p>
<h3 id="TodoForm"><a href="#TodoForm" class="headerlink" title="TodoForm"></a>TodoForm</h3><p>应用还需要一个输入框，在新增的时候输入新TodoItem的title；以及一个可隐藏的输入框用来编辑已有TodoItem的title。</p>
<p>这两个输入框的功能相似，都是维护输入框的值并处理值的更新。不同的是编辑输入框会与某一个TodoItem关联，而新增输入框没有关联对象。</p>
<p>可以使用一个TodoForm的Model来维护两个输入框的状态：</p>
<figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// models/TodoForm.ts</span></span><br><span class="line">import &#123; types, Instance &#125; <span class="keyword">from</span> <span class="string">'mobx-state-tree'</span>;</span><br><span class="line">import &#123; TodoItemInstance &#125; <span class="keyword">from</span> <span class="string">'./TodoItem'</span>;</span><br><span class="line"></span><br><span class="line">export <span class="keyword">const</span> TodoForm = types</span><br><span class="line">  .model(<span class="string">'TodoForm'</span>, &#123;</span><br><span class="line">    <span class="keyword">value</span>: types.optional(types.<span class="keyword">string</span>, <span class="string">''</span>),</span><br><span class="line">    targetTodoId: types.optional(types.maybeNull(types.<span class="keyword">string</span>), <span class="literal">null</span>),</span><br><span class="line">  &#125;)</span><br><span class="line">  .views(self =&gt; (&#123;</span><br><span class="line">    <span class="function"><span class="keyword">get</span> <span class="title">trimedValue</span> (<span class="params"></span>)</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> self.<span class="keyword">value</span>.trim();</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="function"><span class="keyword">get</span> <span class="title">valid</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.trimedValue.length &gt; <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;))</span><br><span class="line">  .actions(self =&gt; (&#123;</span><br><span class="line">    setTarget(target: TodoItemInstance) &#123;</span><br><span class="line">      self.<span class="keyword">value</span> = target.title;</span><br><span class="line">      self.targetTodoId = target.id;</span><br><span class="line">    &#125;,</span><br><span class="line">    update(<span class="keyword">value</span>: <span class="keyword">string</span>) &#123;</span><br><span class="line">      self.<span class="keyword">value</span> = <span class="keyword">value</span>;</span><br><span class="line">    &#125;,</span><br><span class="line">    reset() &#123;</span><br><span class="line">      self.<span class="keyword">value</span> = <span class="string">''</span>;</span><br><span class="line">      self.targetTodoId = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;));</span><br><span class="line"></span><br><span class="line">export type TodoFormInstance = Instance&lt;<span class="keyword">typeof</span> TodoForm&gt;;</span><br></pre></td></tr></table></figure>
<p>TodoForm中，使用value维护输入框的值，targetTodoId表示当前关联的TodoItem的id。并提供了用于关联TodoItem的setTarget方法，更新值的update方法以及重置状态的reset方法。</p>
<p>这里使用了types.optional为状态设置了初始值。</p>
<p>另外还提供了两个计算值：trimedValue以及valid。</p>
<p>这里需要注意的是，在valid的定义中，trimedValue引用的是this而不是self，这是由于valid以及trimedValue两者的定义写在同一个views方法中，在views方法结束前，TypeScript的类型系统并不能观察到self对应的类型中包含valid或者trimedValue，所以需要使用this来代替self。</p>
<p>除了views之外，actions或者volatile也需要注意上面这个问题。</p>
<h3 id="TodoList"><a href="#TodoList" class="headerlink" title="TodoList"></a>TodoList</h3><p>完成上面的两个Model之后，剩下的都是一些与列表相关的状态了。将TodoItem与TodoForm进行组合，构成整个TodoList应用的基本Model：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// models/TodoList.ts</span></span><br><span class="line"><span class="keyword">import</span> &#123; types &#125; <span class="keyword">from</span> <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; TodoItem &#125; <span class="keyword">from</span> <span class="string">'./TodoItem'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; TodoForm &#125; <span class="keyword">from</span> <span class="string">'./TodoForm'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> TodoList = types</span><br><span class="line">  .model(<span class="string">'TodoList'</span>, &#123;</span><br><span class="line">    adderForm: types.optional(TodoForm, &#123;&#125;),</span><br><span class="line">    editorForm: types.optional(TodoForm, &#123;&#125;),</span><br><span class="line">    list: types.array(TodoItem),</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure>
<p>其中adderForm与editorForm分别表示新增Todo与编辑Todo的表单Model，list用于管理Todo列表。</p>
<p>仔细观察目标的成品图，他还包括三个筛选按钮All、Active、Completed，用于筛选展现的Todo列表的类型，这里可以将三种类型定义为枚举TodoFilterType，新建enums.ts文件，输入代码：</p>
<figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// enums.ts</span></span><br><span class="line">export <span class="class"><span class="keyword">enum</span> <span class="title">TodoFilterType</span> </span>&#123;</span><br><span class="line">  All = 'All',</span><br><span class="line">  Active = 'Active',</span><br><span class="line">  Completed = 'Completed'</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然后为TodoList新增一个filterType的状态：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// models/TodoList.ts</span></span><br><span class="line"><span class="keyword">import</span> &#123; types &#125; <span class="keyword">from</span> <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; TodoItem &#125; <span class="keyword">from</span> <span class="string">'./TodoItem'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; TodoForm &#125; <span class="keyword">from</span> <span class="string">'./TodoForm'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; TodoFilterType &#125; <span class="keyword">from</span> <span class="string">'../enums'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> TodoList = types</span><br><span class="line">  .model(<span class="string">'TodoList'</span>, &#123;</span><br><span class="line">    adderForm: types.optional(TodoForm, &#123;&#125;),</span><br><span class="line">    editorForm: types.optional(TodoForm, &#123;&#125;),</span><br><span class="line">    list: types.array(TodoItem),</span><br><span class="line">    filterType: types.optional(types.string, TodoFilterType.All),</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure>
<p>有了这几个基础状态，就可以得到其他几个衍生状态:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// models/TodoList.ts</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> TodoList = types</span><br><span class="line">  .model(<span class="string">'TodoList'</span>, &#123;</span><br><span class="line">    ...</span><br><span class="line">  &#125;)</span><br><span class="line">  .views(<span class="function"><span class="params">self</span> =&gt;</span> (&#123;</span><br><span class="line">    <span class="comment">// 已完成的Todo列表</span></span><br><span class="line">    <span class="keyword">get</span> doneList() &#123;</span><br><span class="line">      <span class="keyword">return</span> self.list.filter(<span class="function"><span class="params">i</span> =&gt;</span> i.done);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 未完成的Todo列表</span></span><br><span class="line">    <span class="keyword">get</span> activeList() &#123;</span><br><span class="line">      <span class="keyword">return</span> self.list.filter(<span class="function"><span class="params">i</span> =&gt;</span> !i.done);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 是否全部完成</span></span><br><span class="line">    <span class="keyword">get</span> isAllDone() &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.doneList.length === self.list.length;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 剩余未完成的Todo数量</span></span><br><span class="line">    <span class="keyword">get</span> activeCount() &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.activeList.length;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 当前展现的Todo列表</span></span><br><span class="line">    <span class="keyword">get</span> showList() &#123;</span><br><span class="line">      <span class="keyword">switch</span> (self.filterType) &#123;</span><br><span class="line">        <span class="keyword">case</span> TodoFilterType.Active:</span><br><span class="line">          <span class="keyword">return</span> <span class="keyword">this</span>.activeList;</span><br><span class="line">        <span class="keyword">case</span> TodoFilterType.Completed:</span><br><span class="line">          <span class="keyword">return</span> <span class="keyword">this</span>.doneList;</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">          <span class="keyword">return</span> self.list;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 是否显示主体UI（没有Todo数据的时候只显示一个新增输入框）</span></span><br><span class="line">    <span class="keyword">get</span> isShowMain() &#123;</span><br><span class="line">      <span class="keyword">return</span> self.list.length &gt; <span class="number">0</span>;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 是否包含已完成的Todo，用于控制右下角[Clear completed]按钮的展现和隐藏</span></span><br><span class="line">    <span class="keyword">get</span> hasDoneTodos() &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.doneList.length &gt; <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;))</span><br></pre></td></tr></table></figure>
<p>最后，再补上更新状态的actions：</p>
<figure class="highlight zephir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// models/TodoList.ts</span></span><br><span class="line">import &#123; types, cast, Instance &#125; from <span class="string">'mobx-state-tree'</span>;</span><br><span class="line">import uuid from <span class="string">'uuid/v1'</span>;</span><br><span class="line">...</span><br><span class="line">export <span class="keyword">const</span> TodoList = types</span><br><span class="line">  .model(<span class="string">'TodoList'</span>, &#123;</span><br><span class="line">    ...</span><br><span class="line">  &#125;)</span><br><span class="line">  .views(<span class="keyword">self</span> =&gt; (&#123;</span><br><span class="line">    ...</span><br><span class="line">  &#125;)</span><br><span class="line">  .actions(<span class="keyword">self</span> =&gt; (&#123;</span><br><span class="line">    <span class="comment">// 切换全部完成/全部未完成</span></span><br><span class="line">    switchAllDone(done?: <span class="keyword">boolean</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (typeof done !== <span class="string">'boolean'</span>) &#123;</span><br><span class="line">        done = !<span class="keyword">self</span>.isAllDone;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">self</span>.<span class="keyword">list</span>.<span class="keyword">forEach</span>(item =&gt; &#123;</span><br><span class="line">        item.switchDone(done);</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 切换列表过滤类型</span></span><br><span class="line">    setFilterType(filterType: TodoFilterType) &#123;</span><br><span class="line">      <span class="keyword">self</span>.filterType = filterType;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 新增Todo</span></span><br><span class="line">    addTodo() &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">self</span>.adderForm.valid) &#123;</span><br><span class="line">        <span class="keyword">self</span>.<span class="keyword">list</span>.push(cast(&#123;</span><br><span class="line">          id: uuid(),</span><br><span class="line">          title: <span class="keyword">self</span>.adderForm.trimedValue,</span><br><span class="line">          done: <span class="keyword">false</span></span><br><span class="line">        &#125;));</span><br><span class="line">        <span class="keyword">self</span>.adderForm.reset();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 更新Todo</span></span><br><span class="line">    updateTodo() &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">self</span>.editorForm.valid) &#123;</span><br><span class="line">        <span class="keyword">const</span> item = <span class="keyword">self</span>.<span class="keyword">list</span>.find(i =&gt; i.id === <span class="keyword">self</span>.editorForm.targetTodoId);</span><br><span class="line">        <span class="keyword">if</span> (item) &#123;</span><br><span class="line">          item.title = <span class="keyword">self</span>.editorForm.trimedValue;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">self</span>.editorForm.reset();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 删除Todo</span></span><br><span class="line">    removeTodo(todoId: string) &#123;</span><br><span class="line">      <span class="keyword">const</span> index = <span class="keyword">self</span>.<span class="keyword">list</span>.findIndex(i =&gt; i.id === todoId);</span><br><span class="line">      <span class="keyword">if</span> (index &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">self</span>.<span class="keyword">list</span>.splice(index, <span class="number">1</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 清除已完成Todos</span></span><br><span class="line">    clearDone() &#123;</span><br><span class="line">      <span class="keyword">self</span>.<span class="keyword">list</span> = cast(<span class="keyword">self</span>.<span class="keyword">list</span>.filter(i =&gt; !i.done));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;));</span><br><span class="line">  </span><br><span class="line">export type TodoListInstance = Instance&lt;typeof TodoList&gt;;</span><br></pre></td></tr></table></figure>
<p>上面的代码在给一些状态赋值的时候，用到了MST提供的cast方法，这个方法仅在TypeScript中有意义，因为他仅仅是将入参的类型转换成对应的状态的类型，使得代码的类型能通过TypeScript的检测（因为在TypeScript看来，没有cast的时候，等号左侧和右侧的两个值并不是类型匹配的）。</p>
<p>另外，在新增Todo的时候，使用了uuid库提供的方法生成Todo的唯一id。注意在项目中安装uuid依赖。</p>
<p>本实例中还依赖了classnames库，也需要一并安装。由于uuid以及classnames库都不包含类型定义文件（*.d.ts），在项目中新增了一个modules.d.ts文件，代码如下：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">// modules.d.ts</span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> <span class="string">'classnames'</span>;</span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> <span class="string">'uuid/v1'</span>;</span><br></pre></td></tr></table></figure>
<h3 id="更新Root"><a href="#更新Root" class="headerlink" title="更新Root"></a>更新Root</h3><p>要在应用中使用上面定义的Model，还需要将他们加入到状态树中，更新models/index.ts文件：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// models/index.ts</span></span><br><span class="line"><span class="keyword">import</span> &#123; types &#125; <span class="keyword">from</span> <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; TodoList &#125; <span class="keyword">from</span> <span class="string">'./TodoList'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> Root = types</span><br><span class="line">  .model(<span class="string">'Root'</span>, &#123;</span><br><span class="line">    todoList: types.optional(TodoList, &#123;&#125;),</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure>
<h2 id="实现UI组件"><a href="#实现UI组件" class="headerlink" title="实现UI组件"></a>实现UI组件</h2><p>UI方面并不是本系列文章的重点，并且本文TodoList的UI实现比较简单，套用了TodoMVC的DOM结构和CSS，本文中只对几个关键的点做一下说明，完整的代码见文末。</p>
<h3 id="尽可能地使用observer装饰组件"><a href="#尽可能地使用observer装饰组件" class="headerlink" title="尽可能地使用observer装饰组件"></a>尽可能地使用observer装饰组件</h3><p>使用mobx-react包提供的observer装饰器装饰后的组件能响应observable的变化，并做了诸多的性能优化，尽可能为你的组件加上observer，除非你想要自定义shouldComponentUpdate来控制组件更新时机。</p>
<h3 id="尽可能地编写无状态组件"><a href="#尽可能地编写无状态组件" class="headerlink" title="尽可能地编写无状态组件"></a>尽可能地编写无状态组件</h3><p>有的同学可能看到过类似这样的说法：</p>
<blockquote>
<p>用Redux管理全局状态，用组件State管理局部状态。</p>
</blockquote>
<p>笔者不认同这种说法，根据笔者的经验来看，当项目复杂到一定的程度，使用State管理的状态会难受到让你抓狂：某个深层次的组件State只能通过改变上层组件传递的props来进行更新。</p>
<p>更何况，现在无状态（state less）组件越来越受到大家的认可，react hooks的出现也顺应了组件无状态的这个发展趋势。</p>
<p>当应用都由无状态组件构成，应用的状态都存储在触手可及的地方（如Redux或MST），想要在某些时刻修改某个状态值就变得轻而易举。</p>
<p>这也是上文中，将输入框的值维护在TodoForm中的一个重要原因。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2019/10/07/MobX-State-Tree数据组件化开发-1-：MST基础/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2019/10/07/MobX-State-Tree数据组件化开发-1-：MST基础/" itemprop="url">[MobX State Tree数据组件化开发][1]：MST基础</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2019-10-07T17:27:12+08:00">
                2019-10-07
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h2 id="预备知识"><a href="#预备知识" class="headerlink" title="预备知识"></a>预备知识</h2><p>在正式进入主题前，你需要确认一下，是否已经掌握下面几个工具和库的使用：</p>
<ul>
<li>MobX：这是MST的核心，MST中存储的响应式“状态”都是MobX的Observable</li>
<li>React：使用React来测试MST的功能非常简单</li>
<li>TypeScript：后文中会使用TS来编写示例代码，TS强大的智能提示和类型检查，有助于快速掌握MST的API</li>
</ul>
<p>上面列举的工具和库都有非常丰富的文档和教程，不太熟悉的同学最好先自学一下。</p>
<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>MST依赖MobX。</p>
<p>项目中执行yarn add mobx mobx-state-tree即可完成安装。</p>
<p>MobX有两个版本，新版本需要浏览器Proxy支持，一些老旧的浏览器并不支持，需要兼容老浏览器的请安装mobx@4：yarn add mobx@4 mobx-state-tree。</p>
<h2 id="Type、Model"><a href="#Type、Model" class="headerlink" title="Type、Model"></a>Type、Model</h2><p>使用MST来维护状态，首先需要让MST知道，这个状态的结构是什么样的。</p>
<p>MST内建了一个类型机制。通过类型的组合就可以定义出整个状态的形状。</p>
<p>并且，在开发环境下，MST可以通过这个定义好的形状，来判断状态的值和形状与其对应的类型是否匹配，确保状态的类型与预期一致，这有助于在开发时及时发现数据类型的问题：</p>
<p><img src="/blog/2019/10/07/MobX-State-Tree数据组件化开发-1-：MST基础/1687eceac84b11ca.jpg" alt></p>
<p>MST提供的一个重要对象就是types，在这个对象中，包含了基础的元类型（primitives types），如string、boolean、number，还包含了一些复杂类型的工厂方法和工具方法，常用的有model、array、map、optional等。</p>
<p>model是一个types中最重要的一个type，使用types.model方法得到的就是Model，在Model中，可以包含多个type或者其他Model。</p>
<p>一个Model可以看作是一个节点（Node），节点之间相互组合，就构造出了整棵状态树（State Tree）。</p>
<p>MST可用的类型和类型方法非常多，这里不一一列举，可以在这里查看完整的列表。</p>
<p>完成Model的定义后，可以使用Model.create方法获得Model的实例。Model.create可以传入两个参数，第一个是Model的初始状态值，第二个参数是可选参数，表示需要给Model及子Model的env对象（环境配置对象），env用于实现简单的依赖注入功能，在后续文章中再详细说明。</p>
<h2 id="Props"><a href="#Props" class="headerlink" title="Props"></a>Props</h2><p>props指的是Model中的属性定义。props定义了这个Model维护的状态对象包含哪些字段，各字段对应的又是什么类型。</p>
<p>拿开篇中的“商品”作为例子：</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; types &#125; from <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> ProductItem = types.model(<span class="string">'ProductItem'</span>, &#123;</span><br><span class="line">    prodName: types.<span class="keyword">string</span>,</span><br><span class="line">    price: types.number,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>types.model方法的第一个参数为Model设定了名称，第二个参数传入了一个对象，这个对象就是Model的props。</p>
<p>上面代码中，指定了ProductItem这个Model包含了类型为string的prodName属性和类型为number的price属性。</p>
<p>注意，可以省略types.model的第二个参数，然后使用model.props方法来定义props。</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> ProductItem = types</span><br><span class="line">    .model(<span class="string">'ProductItem'</span>)</span><br><span class="line">    .props(&#123;</span><br><span class="line">        prodName: types.<span class="keyword">string</span>,</span><br><span class="line">        price: types.number,</span><br><span class="line">    &#125;);</span><br></pre></td></tr></table></figure>
<p>types.model方法的第一个参数为Model设定了名称，第二个参数传入了一个对象，这个对象就是Model的props。</p>
<p>上面代码中，指定了ProductItem这个Model包含了类型为string的prodName属性和类型为number的price属性。</p>
<p>注意，可以省略types.model的第二个参数，然后使用model.props方法来定义props。</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> ProductItem = types</span><br><span class="line">    .model(<span class="string">'ProductItem'</span>)</span><br><span class="line">    .props(&#123;</span><br><span class="line">        prodName: types.<span class="keyword">string</span>,</span><br><span class="line">        price: types.number,</span><br><span class="line">    &#125;);</span><br></pre></td></tr></table></figure>
<p>上面的两份代码得到的ProductItem是相同的（实际上有一些细微差别，但可以完全忽略）。</p>
<p>定义了props之后，在Model的实例上可以访问到相应的字段：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> productItem = ProductItem.create(&#123;<span class="attr">prodName</span>: <span class="string">'商品标题xxx'</span>, <span class="attr">price</span>: <span class="number">99.9</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(productItem.prodName); <span class="comment">// 商品标题xxx</span></span><br><span class="line"><span class="built_in">console</span>.log(productItem.price); <span class="comment">// 99.9</span></span><br></pre></td></tr></table></figure>
<p>上面的两份代码得到的ProductItem是相同的（实际上有一些细微差别，但可以完全忽略）。</p>
<p>定义了props之后，在Model的实例上可以访问到相应的字段：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> productItem = ProductItem.create(&#123;<span class="attr">prodName</span>: <span class="string">'商品标题xxx'</span>, <span class="attr">price</span>: <span class="number">99.9</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(productItem.prodName); <span class="comment">// 商品标题xxx</span></span><br><span class="line"><span class="built_in">console</span>.log(productItem.price); <span class="comment">// 99.9</span></span><br></pre></td></tr></table></figure>
<h2 id="Views"><a href="#Views" class="headerlink" title="Views"></a>Views</h2><p>views是Model中一系列衍生数据或获取衍生数据的方法的集合，类似Vue组件的computed计算属性。</p>
<p>在定义Model时，可以使用model.views方法定义views。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> ProductItem = types</span><br><span class="line">    .model(<span class="string">'ProductItem'</span>, &#123;</span><br><span class="line">        prodName: types.string,</span><br><span class="line">        price: types.number,</span><br><span class="line">        discount: types.number,</span><br><span class="line">    &#125;)</span><br><span class="line">    .views(<span class="function"><span class="params">self</span> =&gt;</span> (&#123;</span><br><span class="line">        <span class="keyword">get</span> priceAfterDiscount () &#123;</span><br><span class="line">            <span class="keyword">return</span> self.price - self.discount;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br></pre></td></tr></table></figure>
<p>上面代码中，定义了priceAfterDiscount，表示商品的折后价格。调用.views方法时，传入的是一个方法，方法的参数self是当前Model的实例，方法需要返回一个对象，表示Model的views集合。</p>
<p>需要注意的是，定义views时有两种选择，使用getter或者不使用。使用getter时，衍生数据的值会被缓存直到依赖的数据发送变化。而不使用时，需要通过方法调用的方式获取衍生数据，无法对计算结果进行缓存。尽可能使用getter，有助于提升应用的性能。</p>
<h2 id="Actions"><a href="#Actions" class="headerlink" title="Actions"></a>Actions</h2><p>actions是用于更新状态的方法集合。</p>
<p>在创建Model时，使用model.actions方法来定义actions：</p>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Root = types</span><br><span class="line">    .model(<span class="symbol">'Root</span>', &#123;</span><br><span class="line">        <span class="built_in">str</span>: types.string,</span><br><span class="line">    &#125;)</span><br><span class="line">    .actions(<span class="keyword">self</span> =&gt; (&#123;</span><br><span class="line">        setStr (val: string) &#123;</span><br><span class="line">            <span class="keyword">self</span>.<span class="built_in">str</span> = val;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br><span class="line">    </span><br><span class="line"><span class="keyword">const</span> root = Root.create(&#123;<span class="built_in">str</span>: <span class="symbol">'mobx</span>'&#125;);</span><br><span class="line">root.setStr(<span class="symbol">'mst</span>');</span><br></pre></td></tr></table></figure>
<p>在安全模式下，所有对状态的更新操作必须在actions中执行，否则会报错：</p>
<p><img src="/blog/2019/10/07/MobX-State-Tree数据组件化开发-1-：MST基础/16882cfe6d826fff.jpg" alt></p>
<p>可以使用unprotect方法解除安全模式（不推荐）：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; types, unprotect &#125; <span class="keyword">from</span> <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Root = types.model(...);</span><br><span class="line">unprotect(Root);</span><br><span class="line"></span><br><span class="line">root.str = <span class="string">'mst'</span>; <span class="comment">// ok</span></span><br></pre></td></tr></table></figure>
<p>除了通常意义上用来更新状态的actions外，在model.actions方法中，还可以设置一些特殊的actions：</p>
<ul>
<li>afterCreate</li>
<li>afterAttach</li>
<li>beforeDetach</li>
<li>beforeDestroy</li>
</ul>
<p>从名字上可以看出来，上面四位都是生命周期方法，可以使用他们在Model的各个生命周期执行一些操作：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Model = types</span><br><span class="line">    .model(...)</span><br><span class="line">    .actions(<span class="function"><span class="params">self</span> =&gt;</span> (&#123;</span><br><span class="line">        afterCreate () &#123;</span><br><span class="line">            <span class="comment">// 执行一些初始化操作</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br></pre></td></tr></table></figure>
<p>具体的MST生命周期在后续文章中再详细讨论。</p>
<h2 id="异步Action、Flow"><a href="#异步Action、Flow" class="headerlink" title="异步Action、Flow"></a>异步Action、Flow</h2><p>异步更新状态是非常常见的需求，MST从底层支持异步action。</p>
<figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> model = types</span><br><span class="line">    .model(...)</span><br><span class="line">    .actions(self =&gt; (&#123;</span><br><span class="line">        <span class="comment">// async/await</span></span><br><span class="line">        <span class="function"><span class="keyword">async</span> <span class="title">getData</span> (<span class="params"></span>)</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">const</span> data = <span class="keyword">await</span> api.getData();</span><br><span class="line">                ...</span><br><span class="line">            &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">                ...</span><br><span class="line">            &#125;</span><br><span class="line">            ...</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="comment">// promise</span></span><br><span class="line">        updateData () &#123;</span><br><span class="line">            <span class="keyword">return</span> api.updateData()</span><br><span class="line">                .then(...)</span><br><span class="line">                .<span class="keyword">catch</span>(...);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br></pre></td></tr></table></figure>
<p>需要注意，上文提到过：</p>
<blockquote>
<p>在安全模式下，所有对状态的更新操作必须在actions中执行，否则会报错</p>
</blockquote>
<p>若使用Promise、async/await来编写异步Action，在异步操作之后更新状态时，代码执行的上下文会脱离action，导致状态在action之外被更新而报错。这里有两种解决办法：</p>
<ol>
<li>将更新状态的操作单独封装成action</li>
<li>编写一个runInAction的action在异步操作中使用</li>
</ol>
<figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 方法1</span></span><br><span class="line">const Model = types</span><br><span class="line">    .model(<span class="params">...</span>)</span><br><span class="line">    .actions(<span class="built_in">self</span> =&gt; (&#123;</span><br><span class="line">        setLoading (loading: <span class="built_in">boolean</span>) &#123;</span><br><span class="line">            <span class="built_in">self</span>.loading = loading;</span><br><span class="line">        &#125;,</span><br><span class="line">        setData (<span class="built_in">data</span>: any) &#123;</span><br><span class="line">            <span class="built_in">self</span>.<span class="built_in">data</span> = <span class="built_in">data</span>;</span><br><span class="line">        &#125;,</span><br><span class="line">        async getData () &#123;</span><br><span class="line">            <span class="params">...</span></span><br><span class="line">            <span class="built_in">self</span>.setLoading(<span class="literal">true</span>); <span class="comment">// 这里因为在异步操作之前，直接赋值self.loading = true也ok</span></span><br><span class="line">            const <span class="built_in">data</span> = await api.getData();</span><br><span class="line">            <span class="built_in">self</span>.setData(<span class="built_in">data</span>);</span><br><span class="line">            <span class="built_in">self</span>.setLoading(<span class="literal">false</span>);</span><br><span class="line">            <span class="params">...</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br><span class="line">    </span><br><span class="line"><span class="comment">// 方法2</span></span><br><span class="line">const Model = types</span><br><span class="line">    .model(<span class="params">...</span>)</span><br><span class="line">    .actions(<span class="built_in">self</span> =&gt; (&#123;</span><br><span class="line">        runInAction (fn: () =&gt; any) &#123;</span><br><span class="line">            fn();</span><br><span class="line">        &#125;,</span><br><span class="line">        async getData () &#123;</span><br><span class="line">            <span class="params">...</span></span><br><span class="line">            <span class="built_in">self</span>.runInAction(() =&gt; <span class="built_in">self</span>.loading = <span class="literal">true</span>);</span><br><span class="line">            const <span class="built_in">data</span> = await api.getData();</span><br><span class="line">            <span class="built_in">self</span>.runInAction(() =&gt; &#123;</span><br><span class="line">                <span class="built_in">self</span>.<span class="built_in">data</span> = <span class="built_in">data</span>;</span><br><span class="line">                <span class="built_in">self</span>.loading = <span class="literal">false</span>;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="params">...</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br></pre></td></tr></table></figure>
<p>方法1需要额外封装N个action，比较麻烦。方法2封装一次就可以多次使用。</p>
<p>但是在某些情况下，两种方法都不够完美：一个异步action被分割成了N个action调用，无法使用MST的插件机制实现整个异步action的原子操作、撤销/重做等高级功能。</p>
<p>为了解决这个问题，MST提供了flow方法来创建异步action：</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">import &#123; types, flow &#125; from <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> model = types</span><br><span class="line">    .model(...)</span><br><span class="line">    .actions(<span class="keyword">self</span> =&gt; &#123;</span><br><span class="line">        <span class="keyword">const</span> getData = flow(<span class="function"><span class="keyword">function</span> * <span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">self</span>.loading = <span class="keyword">true</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">const</span> data = <span class="keyword">yield</span> api.getData();</span><br><span class="line">                <span class="keyword">self</span>.data = data;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">                ...</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">self</span>.loading = <span class="keyword">false</span>;</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            getData</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;)</span><br></pre></td></tr></table></figure>
<p>使用flow方法需要传入一个generator function，在这个生成器方法中，使用yield关键字可以resolve异步操作。并且，在方法中可以直接给状态赋值，写起来更简单自然。</p>
<h2 id="Snapshot"><a href="#Snapshot" class="headerlink" title="Snapshot"></a>Snapshot</h2><p>snapshot即“快照”，表示某一时刻，Model的状态序列化之后的值。这个值是标准的JS对象。</p>
<p>使用getSnapshot方法获取快照：</p>
<figure class="highlight gams"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">import &#123; getSnapshot &#125; from <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"></span><br><span class="line">cosnt <span class="keyword">Model</span> = types.<span class="keyword">model</span>(...);</span><br><span class="line">const <span class="keyword">model</span> = <span class="keyword">Model</span>.create(...);</span><br><span class="line"></span><br><span class="line">console.<span class="built-in">log</span>(getSnapshot(<span class="keyword">model</span>));</span><br></pre></td></tr></table></figure>
<p>使用applySnapshot方法可以更新Model的状态：</p>
<figure class="highlight clean"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; applySnapshot &#125; <span class="keyword">from</span> <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line">applySnapshot(model, &#123;</span><br><span class="line">    msg: <span class="string">'hello'</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>通过applySnapshot方法更新状态时，传入的状态值必须匹配Model的类型定义，否则会报错：</p>
<p><img src="/blog/2019/10/07/MobX-State-Tree数据组件化开发-1-：MST基础/168831d879355c88.jpg" alt></p>
<p>getSnapshot及applySnapshot方法都可以用在Model的子Model上使用。</p>
<h2 id="Volatile-State"><a href="#Volatile-State" class="headerlink" title="Volatile State"></a>Volatile State</h2><p>在MST中，props对应的状态都是可持久化的，也就是可以序列化为标准的JSON数据。并且，props对应的状态必须与props的类型相匹配。<br>如果需要在Model中存储无需持久化，并且数据结构或类型无法预知的动态数据，可以设置为Volatile State。<br>Volatile State使用model.volatile方法定义：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; types &#125; <span class="keyword">from</span> <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; autorun &#125; <span class="keyword">from</span> <span class="string">'mobx'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Model = types</span><br><span class="line">    .model(<span class="string">'Model'</span>)</span><br><span class="line">    .volatile(<span class="function"><span class="params">self</span> =&gt;</span> (&#123;</span><br><span class="line">        anyData: &#123;&#125; <span class="keyword">as</span> <span class="built_in">any</span></span><br><span class="line">    &#125;))</span><br><span class="line">    .actions(<span class="function"><span class="params">self</span> =&gt;</span> (&#123;</span><br><span class="line">      runInAction (fn: <span class="function"><span class="params">()</span> =&gt;</span> <span class="built_in">any</span>) &#123;</span><br><span class="line">        fn();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;));</span><br><span class="line">    </span><br><span class="line"><span class="keyword">const</span> model = Model.create();</span><br><span class="line"></span><br><span class="line">autorun(<span class="function"><span class="params">()</span> =&gt;</span> <span class="built_in">console</span>.log(model.anyData));</span><br><span class="line"></span><br><span class="line">model.runInAction(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  model.anyData = &#123;a: <span class="number">1</span>&#125;;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">model.runInAction(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  model.anyData.a = <span class="number">2</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>和actions及views一样，model.volatile方法也要传入一个参数为Model实例的方法，并返回一个对象。</p>
<p>运行上面代码，可得到如下输出：</p>
<p><img src="/blog/2019/10/07/MobX-State-Tree数据组件化开发-1-：MST基础/168837fdfbd02e5d.jpg" alt></p>
<p>代码中使用Mobx的autorun方法监听并打印model.anyData的值，图中一共看到2次输出：</p>
<p>anyData的初始值<br>第一次更新anyData后的值</p>
<p>但是第二次为anyData.a赋值并没有执行autorun。<br>由此可见，Volatile State的值也是Observable，但是只会响应引用的变化，是一个非Deep Observable。<br>。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2019/10/07/MobX-State-Tree数据组件化开发-0-：开篇/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2019/10/07/MobX-State-Tree数据组件化开发-0-：开篇/" itemprop="url">[MobX State Tree数据组件化开发][0]：开篇</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2019-10-07T16:11:57+08:00">
                2019-10-07
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h2 id="组件化的时代"><a href="#组件化的时代" class="headerlink" title="组件化的时代"></a>组件化的时代</h2><p>React、Vue、Angular等库（框架）出现后，前端进入了UI组件化开发的时代。通过合理地划分应用的功能，封装成一个个从底层到高层的组件，最后构造为一颗组件树，完成我们的应用：</p>
<ul>
<li>App<ul>
<li>Page1<ul>
<li>Component1</li>
<li>Component2</li>
<li>…</li>
</ul>
</li>
</ul>
</li>
<li>Page2</li>
<li>…</li>
</ul>
<p>看起来真棒，不是吗？</p>
<p>但是在实际开发中，还有一道绕不过去的坎：状态管理。怎么组织和划分应用的状态，UI组件如何获得自身需要的状态数据，如何复用状态部件等等，这些问题困扰了我很久。</p>
<h2 id="Flux模式"><a href="#Flux模式" class="headerlink" title="Flux模式"></a>Flux模式</h2><p>当前的状态管理方案中，以Flux模式为主流，代表性的有Redux、Vuex等。</p>
<p>举个例子，假如要实现一个电商系统，这个系统中包含“商品列表”、“收藏夹”两个功能，他们都包含一个元素结构相似的商品列表数据，但是数据的来源（接口）不同。在Redux中，需要这样写：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Reducers## </span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">productList</span> (<span class="params">state = fromJS(&#123;loading: <span class="literal">false</span>, data: []&#125;), &#123;<span class="keyword">type</span>, payload&#125;</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">switch</span> (<span class="keyword">type</span>) &#123;</span><br><span class="line">        <span class="keyword">case</span> types.PRODUCT_GET_LIST_START:</span><br><span class="line">            <span class="keyword">return</span> state.merge(&#123;loading: <span class="literal">true</span>&#125;);</span><br><span class="line">        <span class="keyword">case</span> types.PRODUCT_GET_LIST_SUCCESS:</span><br><span class="line">            <span class="keyword">return</span> state.merge(&#123;loading: <span class="literal">false</span>, data: payload&#125;);</span><br><span class="line">        <span class="keyword">case</span> types.PRODUCT_GET_LIST_FAILURE:</span><br><span class="line">            <span class="keyword">return</span> state.merge(&#123;loading: <span class="literal">false</span>&#125;);</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">return</span> state;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">favorites</span> (<span class="params">state = fromJS(&#123;loading: <span class="literal">false</span>, data: []&#125;), &#123;<span class="keyword">type</span>, payload&#125;</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">switch</span> (<span class="keyword">type</span>) &#123;</span><br><span class="line">        <span class="keyword">case</span> types.FAVORITES_GET_START:</span><br><span class="line">            <span class="keyword">return</span> state.merge(&#123;loading: <span class="literal">true</span>&#125;);</span><br><span class="line">        <span class="keyword">case</span> types.FAVORITES_GET_SUCCESS:</span><br><span class="line">            <span class="keyword">return</span> state.merge(&#123;loading: <span class="literal">false</span>, data: payload&#125;);</span><br><span class="line">        <span class="keyword">case</span> types.FAVORITES_GET_FAILURE:</span><br><span class="line">            <span class="keyword">return</span> state.merge(&#123;loading: <span class="literal">false</span>&#125;);</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">return</span> state;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Actions</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getProducts</span> (<span class="params">params</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function">(<span class="params">dispatch, getState</span>) =&gt;</span> &#123;</span><br><span class="line">        dispatch(&#123;<span class="keyword">type</span>: types.PRODUCT_GET_LIST_START&#125;);</span><br><span class="line">        <span class="keyword">return</span> api.getProducts(params)</span><br><span class="line">            .then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">                dispatch(&#123;<span class="keyword">type</span>: types.PRODUCT_GET_LIST_SUCCESS, payload: res&#125;);</span><br><span class="line">            &#125;)</span><br><span class="line">            .catch(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">                dispatch(&#123;<span class="keyword">type</span>: types.PRODUCT_GET_LIST_FAILURE, payload: err&#125;);</span><br><span class="line">            &#125;);</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getFavorites</span> (<span class="params">params</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function">(<span class="params">dispatch, getState</span>) =&gt;</span> &#123;</span><br><span class="line">        dispatch(&#123;<span class="keyword">type</span>: types.FAVORITES_GET_START&#125;);</span><br><span class="line">        <span class="keyword">return</span> api.getFavorites(params)</span><br><span class="line">            .then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">                dispatch(&#123;<span class="keyword">type</span>: types.FAVORITES_GET_SUCCESS, payload: res&#125;);</span><br><span class="line">            &#125;)</span><br><span class="line">            .catch(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">                dispatch(&#123;<span class="keyword">type</span>: types.FAVORITES_GET_FAILURE, payload: err&#125;);</span><br><span class="line">            &#125;);</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> reducers = combineReducers(&#123;</span><br><span class="line">    productList,</span><br><span class="line">    favorites</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> actions = &#123;</span><br><span class="line">    getProductList,</span><br><span class="line">    getFavorites</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>可以看到，同样是商品列表数据的加载，需要写两份几乎相同的reducer和action。难受，非常难受！</p>
<p>看到这，有的朋友可能会说，可以封装成一个工厂方法来生成呀，比如说：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">creteProductListReducerAndAction</span> (<span class="params">asyncTypes, service, initialState = fromJS(&#123;loading: <span class="literal">false</span>, data: []&#125;)</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> reducer = <span class="function">(<span class="params">state = initialState, &#123;<span class="keyword">type</span>, action&#125;</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (<span class="keyword">type</span>) &#123;</span><br><span class="line">            <span class="keyword">case</span> asyncTypes.START:</span><br><span class="line">                <span class="keyword">return</span> state.merge(&#123;loading: <span class="literal">true</span>&#125;);</span><br><span class="line">            ...</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">const</span> action = <span class="function"><span class="params">params</span> =&gt;</span> dispatch =&gt; &#123;</span><br><span class="line">        dispatch(&#123;<span class="keyword">type</span>: asyncTypes.START&#125;);</span><br><span class="line">        <span class="keyword">return</span> service(params)</span><br><span class="line">            .then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">                dispatch(&#123;<span class="keyword">type</span>: asyncTypes.SUCCESS, payload: res&#125;);</span><br><span class="line">            &#125;)</span><br><span class="line">            .catch(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">                dispatch(&#123;<span class="keyword">type</span>: asyncTypes.FAILURE, payload: err&#125;);</span><br><span class="line">            &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> &#123;reducer, action&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>乍一看也还可以接受，但是如果有一天，我想要扩展一下favorites的reducer呢？当应用开始变得愈发丰满，需要不断地改造工厂方法才能满足业务的需求。</p>
<p>上面的例子比较简单，当然还有更好的方案，社区也有诸如dva的框架产出，但是都不够完美：复用和扩展状态部件非常困难。</p>
<h2 id="MobX-State-Tree：数据组件化"><a href="#MobX-State-Tree：数据组件化" class="headerlink" title="MobX State Tree：数据组件化"></a>MobX State Tree：数据组件化</h2><p>类似UI组件化，数据组件化很好地解决了Store模式难以复用和扩展的问题。像一个React组件，很容易在组件树的各个位置重复使用。使用HOC等手段，也能方便地对组件自身的功能进行扩展。</p>
<p>本系列文章的主角：MobX State Tree（后文中简称MST）正是实现数据组件化的利器。</p>
<blockquote>
<p>React, but for data.</p>
</blockquote>
<p>MST被称为数据管理的React，他建立在MobX的基础之上，吸收了Redux等工具的优点（state序列化、反序列化、时间旅行等，甚至能够直接替换Redux使用，见<a href="https://github.com/mobxjs/mobx-state-tree/blob/master/packages/mst-example-redux-todomvc/src/index.js#L20" target="_blank" rel="noopener">redux-todomvc example</a>）。</p>
<p>对于MST的具体细节，在开篇中就不赘述了，先来看看如何用MST来编写上文中的“商品列表”和“收藏夹”的数据容器：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; types, applySnapshot &#125; <span class="keyword">from</span> <span class="string">'mobx-state-tree'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 消息通知 BaseModel</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> Notification = types</span><br><span class="line">    .model(<span class="string">'Notification'</span>)</span><br><span class="line">    .views(<span class="function"><span class="params">self</span> =&gt;</span> (&#123;</span><br><span class="line">        <span class="keyword">get</span> notification () &#123;</span><br><span class="line">            <span class="keyword">return</span> &#123;</span><br><span class="line">                success (msg) &#123;</span><br><span class="line">                    <span class="built_in">console</span>.log(msg);</span><br><span class="line">                &#125;,</span><br><span class="line">                error (msg) &#123;</span><br><span class="line">                    <span class="built_in">console</span>.error(msg);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 可加载 BaseModel</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> Loadable = types</span><br><span class="line">    .model(<span class="string">'Loadable'</span>, &#123;</span><br><span class="line">        loading: types.optional(types.boolean, <span class="literal">false</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">    .actions(<span class="function"><span class="params">self</span> =&gt;</span> (&#123;</span><br><span class="line">        setLoading (loading: boolean) &#123;</span><br><span class="line">            self.loading = loading;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 远端资源 BaseModel</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> RemoteResource = types.compose(Loadable, Notification)</span><br><span class="line">    .named(<span class="string">'RemoteResource'</span>)</span><br><span class="line">    .action(<span class="function"><span class="params">self</span> =&gt;</span> (&#123;</span><br><span class="line">        <span class="keyword">async</span> fetch (...args) &#123;</span><br><span class="line">            self.setLoading(<span class="literal">true</span>);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// self.serviceCall为获取数据的接口方法</span></span><br><span class="line">                <span class="comment">// 需要在扩展RemoteResource时定义在action</span></span><br><span class="line">                <span class="keyword">const</span> res = <span class="keyword">await</span> self.serviceCall(...args);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// self.data用于保存返回的数据</span></span><br><span class="line">                <span class="comment">// 需要在扩展RemoteResource时定义在props中</span></span><br><span class="line">                applySnapshot(self.data, res);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">                self.notification.error(err);</span><br><span class="line">            &#125;</span><br><span class="line">            self.setLoading(<span class="literal">false</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br><span class="line">    </span><br><span class="line"><span class="comment">// 商品Model</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> ProductItem = types.model(<span class="string">'ProductItem'</span>, &#123;</span><br><span class="line">    prodName: types.string,</span><br><span class="line">    price: types.number,</span><br><span class="line">    ...</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 商品列表数据Model</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> ProductItemList = RemoteResource</span><br><span class="line">    .named(<span class="string">'ProductItemList'</span>)</span><br><span class="line">    .props(&#123;</span><br><span class="line">        data: types.array(ProductItem),</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 商品列表Model</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> ProductList = ProductItemList</span><br><span class="line">    .named(<span class="string">'ProductList'</span>)</span><br><span class="line">    .actions(<span class="function"><span class="params">self</span> =&gt;</span> (&#123;</span><br><span class="line">        serviceCall (params) &#123;</span><br><span class="line">            <span class="keyword">return</span> apis.getProductList(params);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 收藏夹Model</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> Favorites = ProductItemList</span><br><span class="line">    .named(<span class="string">'Favorites'</span>)</span><br><span class="line">    .actions(<span class="function"><span class="params">self</span> =&gt;</span> (&#123;</span><br><span class="line">        serviceCall (params) &#123;</span><br><span class="line">            <span class="keyword">return</span> apis.getFavorites(params);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;));</span><br></pre></td></tr></table></figure>
<p>一不小心，代码写得比Redux版本还要多了[捂脸]，但是仔细看看，上面的代码中封装了一些细粒度的组件，然后通过组合和扩展，几行代码就得到了我们想要的“商品列表”和“收藏夹”的数据容器。</p>
<p>在MST中，一个“数据组件”被称为“Model”，Model的定义采用了链式调用的方式，并且能重复定义props、views、actions等，MST会在内部将多次的定义进行合并处理，成为一个新的Model。</p>
<p>再来看上面的实现代码，代码中定义了三个BaseModel（提供基础功能的Model），Notification、Loadable以及RemoteResource。其中Notification提供消息通知的功能，Loadable提供了loading状态以及切换loading状态的方法，而RemoteResource在前两者的基础上，提供了加载远程资源的能力。</p>
<p>三个BaseModel的实现非常简单，并且与业务逻辑零耦合。最后，通过组合BaseModel并扩展出对应的功能，实现了ProductList与Favorites两个Model。</p>
<p>在构造应用的时候，把应用的功能拆分成这样一个个简单的BaseModel，这样应用的代码看起来就会赏心悦目并且更易于维护。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2019/10/03/javascript-观察者模式/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2019/10/03/javascript-观察者模式/" itemprop="url">JavaScript 观察者模式</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2019-10-03T15:08:00+08:00">
                2019-10-03
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/javascript/" itemprop="url" rel="index">
                    <span itemprop="name">javascript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h1 id="观察者模式"><a href="#观察者模式" class="headerlink" title="观察者模式"></a>观察者模式</h1><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Observer</span> </span>&#123;</span><br><span class="line">      <span class="keyword">constructor</span>(fns) &#123;</span><br><span class="line">        <span class="keyword">this</span>.fns = fns || [];</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">//订阅</span></span><br><span class="line">      subscribe(fn) &#123;</span><br><span class="line">        <span class="keyword">this</span>.fns.push(fn);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 取消订阅</span></span><br><span class="line">      unsubscribe(fn) &#123;</span><br><span class="line">        <span class="keyword">this</span>.fns = <span class="keyword">this</span>.fns.filter(<span class="function">(<span class="params">el</span>) =&gt;</span> &#123;</span><br><span class="line">          <span class="keyword">return</span> el !== fn;</span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 发射</span></span><br><span class="line">      update(o) &#123;</span><br><span class="line">        <span class="keyword">this</span>.fns.forEach(<span class="function">(<span class="params">fn</span>) =&gt;</span> &#123;</span><br><span class="line">          fn(o);</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//测试</span></span><br><span class="line">    <span class="keyword">var</span> o = <span class="keyword">new</span> Observer;</span><br><span class="line">    <span class="keyword">var</span> f1 = <span class="function"><span class="keyword">function</span> (<span class="params">data</span>) </span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'Robbin: '</span> + data + <span class="string">', 赶紧干活了！'</span>);</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> f2 = <span class="function"><span class="keyword">function</span> (<span class="params">data</span>) </span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'Randall: '</span> + data + <span class="string">', 找他加点工资去！'</span>);</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    o.subscribe(f1);</span><br><span class="line">    o.subscribe(f2);</span><br><span class="line"></span><br><span class="line">    o.update(<span class="string">"Tom回来了！"</span>, <span class="string">'666'</span>); </span><br><span class="line">    <span class="comment">//Robbin: Tom回来了！, 赶紧干活了！ </span></span><br><span class="line">    <span class="comment">//Randall: Tom回来了！, 找他加点工资去！</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//退订f1</span></span><br><span class="line">    o.unsubscribe(f1);</span><br><span class="line">    <span class="comment">//再来验证</span></span><br><span class="line">    o.update(<span class="string">"Tom回来了！"</span>); <span class="comment">// Randall: Tom回来了！, 找他加点工资去！</span></span><br><span class="line">    o.unsubscribe(f2);</span><br><span class="line">    o.update(<span class="string">"Tom回来了！"</span>); <span class="comment">// 无打印，因为取消了所有的订阅</span></span><br></pre></td></tr></table></figure>
<h3 id="通过代理来实现："><a href="#通过代理来实现：" class="headerlink" title="通过代理来实现："></a>通过代理来实现：</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//添加观察者</span></span><br><span class="line">  <span class="keyword">const</span> queuedObservers = <span class="keyword">new</span> <span class="built_in">Set</span>();</span><br><span class="line">  <span class="keyword">const</span> observe = <span class="function"><span class="params">fn</span> =&gt;</span> queuedObservers.add(fn);</span><br><span class="line"></span><br><span class="line">  <span class="comment">//proxy 的set 方法</span></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">set</span>(<span class="params">target, key, value, receiver</span>) </span>&#123;</span><br><span class="line">      <span class="keyword">const</span> result = <span class="built_in">Reflect</span>.set(target, key, value, receiver);</span><br><span class="line">      queuedObservers.forEach(<span class="function"><span class="params">observer</span> =&gt;</span> observer());</span><br><span class="line">      <span class="keyword">return</span> result;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//创建proxy代理</span></span><br><span class="line">  <span class="keyword">const</span> observable = <span class="function"><span class="params">obj</span> =&gt;</span> <span class="keyword">new</span> <span class="built_in">Proxy</span>(obj, &#123;<span class="keyword">set</span>&#125;);</span><br><span class="line">  //被观察的 对象</span><br><span class="line">  const person = observable(&#123;</span><br><span class="line">      name: <span class="string">'张三'</span>,</span><br><span class="line">      age: <span class="number">20</span></span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">print</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">`<span class="subst">$&#123;person.name&#125;</span>, <span class="subst">$&#123;person.age&#125;</span>`</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">print2</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">`我是二号观察者：<span class="subst">$&#123;person.name&#125;</span>, <span class="subst">$&#123;person.age&#125;</span>`</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//添加观察者</span></span><br><span class="line">  observe(print);</span><br><span class="line">  observe(print2);</span><br><span class="line">  person.name = <span class="string">'李四'</span>;</span><br></pre></td></tr></table></figure>
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2019/10/03/Typescript 代理模式/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2019/10/03/Typescript 代理模式/" itemprop="url">JavaScript 代理模式</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2019-10-03T15:08:00+08:00">
                2019-10-03
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/javascript/" itemprop="url" rel="index">
                    <span itemprop="name">javascript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h1 id="代理模式"><a href="#代理模式" class="headerlink" title="代理模式"></a>代理模式</h1><p>代理模式: 代理模式给某一个对象提供一个代理对象，并由代理对象控制对原对象的引用。</p>
<h3 id="实际场景"><a href="#实际场景" class="headerlink" title="实际场景"></a>实际场景</h3><p>实体创建比较费时：在等待期间给出提示；<br>本体创建出来占用内存过大： 等到用到这个实体的时候再去创建。<br>系统的权限控制： 用来过滤请求</p>
<h3 id="代理模式的结构"><a href="#代理模式的结构" class="headerlink" title="代理模式的结构"></a>代理模式的结构</h3><p>抽象角色：通过接口或抽象类声明真实角色实现的业务方法。<br>代理角色：实现抽象角色，是真实角色的代理，通过真实角色的业务逻辑方法来实现抽象方法，并可以附加自己的操作。<br>真实角色：实现抽象角色，定义真实角色所要实现的业务逻辑，供代理角色调用<br>代理模式的例子<br>现在我们要创建一个图书管理系统。</p>
<p>管理系统初始化时间较长。<br>普通用户只能查找，管理员可以添加和删除。<br>管理系统接口<br><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* i-book-library.ts */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">interface</span> IBookLibrary &#123;</span><br><span class="line">    getBook: <span class="function">(<span class="params">bookName: <span class="built_in">string</span></span>) =&gt;</span> <span class="built_in">any</span>;</span><br><span class="line">    addBook: <span class="function">(<span class="params">bookName: <span class="built_in">string</span></span>) =&gt;</span> <span class="built_in">any</span>;</span><br><span class="line">    removeBook: <span class="function">(<span class="params">bookName: <span class="built_in">string</span></span>) =&gt;</span> <span class="built_in">any</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>真实角色实现接口<br><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* book-library.ts */</span></span><br><span class="line"><span class="keyword">import</span> IBookLibrary <span class="keyword">from</span> <span class="string">'./i-book-library'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> BookLibrary <span class="keyword">implements</span> IBookLibrary&#123;</span><br><span class="line">    <span class="comment">// 实例可能占用内存较多</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> bookList: <span class="built_in">string</span>[];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!BookLibrary.bookList) &#123;</span><br><span class="line">            BookLibrary.bookList = [];</span><br><span class="line">            <span class="comment">// 创建实例时间长</span></span><br><span class="line">            <span class="keyword">const</span> time = <span class="built_in">Date</span>.now() + <span class="number">10000</span>;</span><br><span class="line">            <span class="keyword">while</span> (<span class="built_in">Date</span>.now() &lt; time) &#123;&#125;;</span><br><span class="line">            BookLibrary.bookList.push(<span class="string">'Harry Potter'</span>);</span><br><span class="line">            BookLibrary.bookList.push(<span class="string">'Animal Spirits'</span>);</span><br><span class="line">            BookLibrary.bookList.push(<span class="string">'Tuesdays with Morrie'</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> getBook(bookName: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> index = BookLibrary.bookList.findIndex(<span class="function"><span class="params">book</span> =&gt;</span> book === bookName);</span><br><span class="line">        <span class="keyword">if</span> (index !== <span class="number">-1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> BookLibrary.bookList[index];</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`can not find <span class="subst">$&#123;bookName&#125;</span>`</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> addBook(bookName: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> index = BookLibrary.bookList.findIndex(<span class="function"><span class="params">book</span> =&gt;</span> book === bookName);</span><br><span class="line">        <span class="keyword">if</span> (index !== <span class="number">-1</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`already has <span class="subst">$&#123;bookName&#125;</span>`</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            BookLibrary.bookList.push(bookName);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> removeBook(bookName: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> index = BookLibrary.bookList.findIndex(<span class="function"><span class="params">book</span> =&gt;</span> book === bookName);</span><br><span class="line">        <span class="keyword">if</span> (index !== <span class="number">-1</span>) &#123;</span><br><span class="line">            BookLibrary.bookList.splice(index, <span class="number">1</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`can not find <span class="subst">$&#123;bookName&#125;</span>`</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>代理角色<br><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> BookLibrary <span class="keyword">from</span> <span class="string">'./book-library'</span>;</span><br><span class="line"><span class="keyword">import</span> IBookLibrary <span class="keyword">from</span> <span class="string">'./i-book-library'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> BookProxy <span class="keyword">implements</span> IBookLibrary &#123;</span><br><span class="line">    <span class="keyword">private</span> readonly bookLibrary: BookLibrary;</span><br><span class="line">    <span class="keyword">private</span> readonly isAdmin: <span class="built_in">boolean</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">isAdmin?: <span class="built_in">boolean</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.isAdmin = isAdmin;</span><br><span class="line">        <span class="comment">// 给出提示消息</span></span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'正在创建中，请等待'</span>);</span><br><span class="line">        <span class="keyword">this</span>.bookLibrary = <span class="keyword">new</span> BookLibrary();</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'创建完成'</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> getBook(bookName: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.bookLibrary.getBook(bookName);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> addBook(bookName: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="keyword">this</span>.isAdmin) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Sorry, you have no right to operate'</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.bookLibrary.addBook(bookName);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> removeBook(bookName: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="keyword">this</span>.isAdmin) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Sorry, you have no right to operate'</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.bookLibrary.removeBook(bookName);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>客户端调用<br><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* client.ts */</span></span><br><span class="line"><span class="keyword">import</span> BookProxy from <span class="string">'./book-proxy'</span>;</span><br><span class="line"></span><br><span class="line">export <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">Client</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">static</span> <span class="title">userTest</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">const</span> proxy = <span class="keyword">new</span> BookProxy();</span><br><span class="line">        <span class="function"><span class="keyword">return</span> proxy.<span class="title">getBook</span><span class="params">(<span class="string">'Animal Spirits'</span>)</span></span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">static</span> <span class="title">adminTest</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">const</span> proxy = <span class="keyword">new</span> BookProxy(<span class="keyword">true</span>);</span><br><span class="line">        proxy.getBook(<span class="string">'Animal Spirits'</span>);</span><br><span class="line">        proxy.addBook(<span class="string">'Lord of the flies'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>//Client.userTest();</p>
<p>Client.adminTest();<br>代理模式和装饰者模式的区别<br>代理模式和装饰者模式都是对真实对象进行修饰。<br>代理模式一般不会添加额外的方法，最多会加一些权限校验的方法。而装饰者模式就是为了对真实对象扩展而存在的。</p>
<p>代理模式的利弊<br>利：代理模式可以推迟大内存对象的创建到其他元素加载完毕之后，这往往能给用户带来一种速度大幅提升的感觉。<br>在较长时间的操作增加“正在加载等提示”。将权限系统的权限判断和实际操作分离开。<br>弊：将大对象推迟创建后，用户在第一次使用时会感觉很慢而大吃一惊。在不恰当的场合使用会增加无谓的复杂性，还不如直接访问本体轻松。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/28/简单聊计算/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/09/28/简单聊计算/" itemprop="url">简单聊计算</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-28T22:13:26+08:00">
                2018-09-28
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/数据结构与算法/" itemprop="url" rel="index">
                    <span itemprop="name">数据结构与算法</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h1 id="算法"><a href="#算法" class="headerlink" title="算法"></a>算法</h1><h2 id="算法-信息处理"><a href="#算法-信息处理" class="headerlink" title="算法 = 信息处理"></a>算法 = 信息处理</h2><ul>
<li>借助某种工具，遵照一定规则，以明确而机械的形式进行</li>
</ul>
<h2 id="计算模型-计算机-信息处理工具"><a href="#计算模型-计算机-信息处理工具" class="headerlink" title="计算模型 = 计算机 = 信息处理工具"></a>计算模型 = 计算机 = 信息处理工具</h2><h2 id="所谓算法，即特定计算模型下，旨在解决特定问题的指令序列"><a href="#所谓算法，即特定计算模型下，旨在解决特定问题的指令序列" class="headerlink" title="所谓算法，即特定计算模型下，旨在解决特定问题的指令序列"></a>所谓算法，即特定计算模型下，旨在解决特定问题的指令序列</h2><ul>
<li>输入 待处理的信息（问题）</li>
<li>输出 经处理的信息（答案）</li>
<li>正确性 的确可以解决指定的问题</li>
<li>确定性 任一算法都可以描述为一个由基本操作组成的序列</li>
<li>可行性 每一基本操作都可实现，且在常数时间内完成</li>
<li>有穷性 对于任何输入，经有穷次基本操作，都可以得到输出</li>
</ul>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
  </section>

  
  <nav class="pagination">
    <span class="page-number current">1</span><a class="page-number" href="/blog/page/2/">2</a><span class="space">&hellip;</span><a class="page-number" href="/blog/page/4/">4</a><a class="extend next" rel="next" href="/blog/page/2/"><i class="fa fa-angle-right"></i></a>
  </nav>



          </div>
          


          

        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      

      <section class="site-overview-wrap sidebar-panel sidebar-panel-active">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <p class="site-author-name" itemprop="name">lhweb</p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/blog/archives/">
              
                  <span class="site-state-item-count">39</span>
                  <span class="site-state-item-name">posts</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/blog/categories/index.html">
                  <span class="site-state-item-count">6</span>
                  <span class="site-state-item-name">categories</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/blog/tags/index.html">
                  <span class="site-state-item-count">25</span>
                  <span class="site-state-item-name">tags</span>
                </a>
              </div>
            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/blog/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          

          
          

          
          

          

        </div>
      </section>

      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2019</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">lhweb</span>

  
</div>


  <div class="powered-by">Powered by <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a></div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">Theme &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Muse</a> v5.1.4</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/blog/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/blog/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/blog/js/src/motion.js?v=5.1.4"></script>



  
  

  

  


  <script type="text/javascript" src="/blog/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  





  

  

  

  
  

  

  

  

</body>
</html>
